Wiring diagram

Hello, do you have an example diagram for the wiring, I'm very interested in getting this working.

The RF300 has only 2 leads, one + and the pther ground but you must not connect directly +12 to the + lead. Put a resistor between so the tension in the RF300 may be modulated.
I pass this signal through a capacitor to remove the dc voltage and send the resulting signal to an operational Amplifier. probably a voltage follower is enough but a bit of amplification may be good. Will look at the schema but i was quite straighforward. Do you have access to an Oscilloscope?. Was invaluable.

Thanks @fgorina, yeah I have a Siglent SDS2104X Plus.
I have a LF3000, which is the outboard version of the RF300.
https://www.simrad-yachting.com/simrad/type/autopilots/drive-units/lf3000/
lf3000mk2_revA.pdf

I'm thinking that the interface box must be specific to the LF3000 (does the RF300 have one?). So I would connect the RF+ and RF- signals that are the output of the interface box, correct?
Thanks for the help on this. I was using the SeaStar universal SmartStick, but I have had two of them fail, they seem to last from 6 months to a year. When calling SeaStar, they have absolutely no one that can help, and they wont warranty them.  I have a brand new LF3000, and it looks like with your setup that it would work.
Just so I understand, you connect like this:
[12V+] -> [Resistor] -> [RF+]

[GND] <----------------------------+- [RF-]
                                   |
[ESP32] <- [Amp] <- [Capacitor] <- +

How clean is the signal? I'll put my scope on it when I get home and look.
When you have time, I am interested to know what you used for the resistor, cap, and how you setup the amp.
Thanks again,
Trevor

Signal may be ok but you will need to play with the resistor and capacitors.
After processing the signal with the Op I put a "fast" optocoupler so I have nothing bad entering the ESP.

It works quite well. Also the Ootpcoupler finish to give a very good signal to measure the frequency.

It is better to asses the "central" ffrequency with the oscilloscope. My documentation said one thing and measured was another.

I process convert the output frequency to a duty cycle with the pwm and filter to get a voltage to the PyPilot!!!

On Oct 10 2024, at 5:18 PM, Trevor Flynn  @.***>  wrote:

Thanks  @fgorina (https://github.com/fgorina), yeah I have a Siglent SDS2104X Plus.
I have a LF3000, which is the outboard version of the RF300.
https://www.simrad-yachting.com/simrad/type/autopilots/drive-units/lf3000/
lf3000mk2_revA.pdf (https://github.com/user-attachments/files/17330226/lf3000mk2_revA.pdf)
image.png (view on web) (https://github.com/user-attachments/assets/b71cfc93-5b0b-4c6e-b641-c47912f214c6)
image.png (view on web) (https://github.com/user-attachments/assets/cb9e52a6-d880-40a2-9e4a-b9f50b34c949)
I'm thinking that the interface box must be specific to the LF3000 (does the RF300 have one?). So I would connect the RF+ and RF- signals that are the output of the interface box, correct?
Thanks for the help on this. I was using the SeaStar universal SmartStick, but I have had two of them fail, they seem to last from 6 months to a year. When calling SeaStar, they have absolutely no one that can help, and they wont warranty them. I have a brand new LF3000, and it looks like with your setup that it would work.
Just so I understand, you connect like this:
[12V+] ->  [Resistor] ->  [RF+]
[GND]  <----------------------------+- [RF-]
|
[ESP32]  <- [Amp]  <- [Capacitor]  <- +
How clean is the signal? I'll put my scope on it when I get home and look.
When you have time, I am interested to know what you used for the resistor, cap, and how you setup the amp.
Thanks again,
Trevor
—
Reply to this email directly,     view it on GitHub (https://github.com/fgorina/RF300-Converter/issues/1#issuecomment-2405415483), or     unsubscribe (https://github.com/notifications/unsubscribe-auth/AACXYU2W4NGQJE57KKSNABDZ22LF3AVCNFSM6AAAAABPCDWMWKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDIMBVGQYTKNBYGM).
You are receiving this because you were mentioned.

There is a README in the test folder, no Idea how it arrived there, that explains a bit more

On Oct 10 2024, at 6:09 PM, Paco Gorina  @.***>  wrote:

Signal may be ok but you will need to play with the resistor and capacitors.
After processing the signal with the Op I put a "fast" optocoupler so I have nothing bad entering the ESP.

It works quite well. Also the Ootpcoupler finish to give a very good signal to measure the frequency.

It is better to asses the "central" ffrequency with the oscilloscope. My documentation said one thing and measured was another.

I process convert the output frequency to a duty cycle with the pwm and filter to get a voltage to the PyPilot!!!

On Oct 10 2024, at 5:18 PM, Trevor Flynn  @.***>  wrote:

Thanks   @fgorina (https://github.com/fgorina), yeah I have a Siglent SDS2104X Plus.
I have a LF3000, which is the outboard version of the RF300.
https://www.simrad-yachting.com/simrad/type/autopilots/drive-units/lf3000/
lf3000mk2_revA.pdf (https://github.com/user-attachments/files/17330226/lf3000mk2_revA.pdf)
image.png (view on web) (https://github.com/user-attachments/assets/b71cfc93-5b0b-4c6e-b641-c47912f214c6)
image.png (view on web) (https://github.com/user-attachments/assets/cb9e52a6-d880-40a2-9e4a-b9f50b34c949)
I'm thinking that the interface box must be specific to the LF3000 (does the RF300 have one?). So I would connect the RF+ and RF- signals that are the output of the interface box, correct?
Thanks for the help on this. I was using the SeaStar universal SmartStick, but I have had two of them fail, they seem to last from 6 months to a year. When calling SeaStar, they have absolutely no one that can help, and they wont warranty them. I have a brand new LF3000, and it looks like with your setup that it would work.
Just so I understand, you connect like this:
[12V+] ->  [Resistor] ->  [RF+]
[GND]  <----------------------------+- [RF-]
                                                                   |

[ESP32]  <- [Amp]  <- [Capacitor]  <- +
How clean is the signal? I'll put my scope on it when I get home and look.
When you have time, I am interested to know what you used for the resistor, cap, and how you setup the amp.
Thanks again,
Trevor
—
Reply to this email directly,     view it on GitHub (https://github.com/fgorina/RF300-Converter/issues/1#issuecomment-2405415483), or     unsubscribe (https://github.com/notifications/unsubscribe-auth/AACXYU2W4NGQJE57KKSNABDZ22LF3AVCNFSM6AAAAABPCDWMWKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDIMBVGQYTKNBYGM).
You are receiving this because you were mentioned.

I found this file. Hope it works

On Oct 10 2024, at 6:12 PM, Paco Gorina  @.***>  wrote:

There is a README in the test folder, no Idea how it arrived there, that explains a bit more

On Oct 10 2024, at 6:09 PM, Paco Gorina  @.***>  wrote:

Signal may be ok but you will need to play with the resistor and capacitors.
After processing the signal with the Op I put a "fast" optocoupler so I have nothing bad entering the ESP.

It works quite well. Also the Ootpcoupler finish to give a very good signal to measure the frequency.

It is better to asses the "central" ffrequency with the oscilloscope. My documentation said one thing and measured was another.

I process convert the output frequency to a duty cycle with the pwm and filter to get a voltage to the PyPilot!!!

On Oct 10 2024, at 5:18 PM, Trevor Flynn  @.***>  wrote:

Thanks   @fgorina (https://github.com/fgorina), yeah I have a Siglent SDS2104X Plus.
I have a LF3000, which is the outboard version of the RF300.
https://www.simrad-yachting.com/simrad/type/autopilots/drive-units/lf3000/
lf3000mk2_revA.pdf (https://github.com/user-attachments/files/17330226/lf3000mk2_revA.pdf)
image.png (view on web) (https://github.com/user-attachments/assets/b71cfc93-5b0b-4c6e-b641-c47912f214c6)
image.png (view on web) (https://github.com/user-attachments/assets/cb9e52a6-d880-40a2-9e4a-b9f50b34c949)
I'm thinking that the interface box must be specific to the LF3000 (does the RF300 have one?). So I would connect the RF+ and RF- signals that are the output of the interface box, correct?
Thanks for the help on this. I was using the SeaStar universal SmartStick, but I have had two of them fail, they seem to last from 6 months to a year. When calling SeaStar, they have absolutely no one that can help, and they wont warranty them. I have a brand new LF3000, and it looks like with your setup that it would work.
Just so I understand, you connect like this:
[12V+] ->  [Resistor] ->  [RF+]
[GND]  <----------------------------+- [RF-]
                                                                   |

[ESP32]  <- [Amp]  <- [Capacitor]  <- +
How clean is the signal? I'll put my scope on it when I get home and look.
When you have time, I am interested to know what you used for the resistor, cap, and how you setup the amp.
Thanks again,
Trevor
—
Reply to this email directly,     view it on GitHub (https://github.com/fgorina/RF300-Converter/issues/1#issuecomment-2405415483), or     unsubscribe (https://github.com/notifications/unsubscribe-auth/AACXYU2W4NGQJE57KKSNABDZ22LF3AVCNFSM6AAAAABPCDWMWKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDIMBVGQYTKNBYGM).
You are receiving this because you were mentioned.

Thanks for the help. If you tried to attach a file I don't think it came through. I read the README in the test folder. I'm going to try to get this setup and working. What opamp did you use? Just wondering on a working setup for it.
I'll order some of these optoisolators: https://www.digikey.com/en/products/detail/onsemi/H11L1M/284866
I assume you are using a ceramic cap? What are good starting values for the resistor and cap?

Thank you for the values. Unfortunately, I do not think attachments to email responses make it to the github issue.
I think I understand the setup though, thank you for the help.
Next time I am home I'll setup a test. Currently, I'll be traveling for the next few weeks (Seems like all I ever do anymore is travel...)
Thanks again for the help!

I will try to put the schme in Github

On Oct 11 2024, at 7:44 PM, Trevor Flynn  @.***>  wrote:

Thank you for the values. Unfortunately, I do not think attachments to email responses make it to the github issue.
I think I understand the setup though, thank you for the help.
Next time I am home I'll setup a test. Currently, I'll be traveling for the next few weeks (Seems like all I ever do anymore is travel...)
Thanks again for the help!
—
Reply to this email directly,     view it on GitHub (https://github.com/fgorina/RF300-Converter/issues/1#issuecomment-2407869937), or     unsubscribe (https://github.com/notifications/unsubscribe-auth/AACXYU5CPVPR5TG34FIDPQ3Z3AFBPAVCNFSM6AAAAABPCDWMWKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDIMBXHA3DSOJTG4).
You are receiving this because you were mentioned.

I see the diagram. I think I understand the circuit. I will take some time and play with it once I am done traveling.
Again, I really appreciate the help.

@fgorina OK I've made progress on this.
This is my current wiring diagram. I am getting very good results with this on the LR3000.

I only used a single input filter, I am not using a second stage filter between the op amp and the opto isolator. Perhaps it could use one, but I am not seeing any issues right now. The load on the R1 resistor is about 31mA. I've decided to use a 1 watt resistor for it (.03a * 12v = 0.36watts) I'm not hitting close to 1 watt, but I am noticing that my smaller resistors were getting hot, and the 1 watt resistor is not getting as warm.
As expected on the LR3000+/- terminals, they seem to be interchangeable with no difference in the signal. Most of the signal distortion is caused by the first stage filter. A better sized filter would help a lot with getting a cleaner signal, but the opto-isolator makes a nice square wave and does not really care about how noisy the signal is leaving the op amp, so the only thing that it would do is get a more symmetric duty cycle out of the opto isolator. But in this application, we do not care about the duty cycle, only the frequency, so it does not matter. The first stage filter is doing a great job at removing the DC component of the signal and that is all that matters.
This is my current code I am using on the ESP32 for getting the rudder angle. I'm getting solid results. My next task will be to add NMEA 2000 support to the code. I threw a webserver onto it so I can calibrate it once I have the final circuit installed on the boat. Since I am using an off the shelf autopilot, the NMEA2000 interface will be the next required task. I have ordered some CAN32 boards and will test the code on them next.
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/pcnt.h"
#include "esp_system.h"
#include "esp_timer.h"
#include "driver/uart.h"
#include "driver/gpio.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_http_server.h"
#include "string.h"
#include "esp_netif.h"
#include "esp_wifi_types.h"

#define PCNT_UNIT      PCNT_UNIT_0
#define PCNT_INPUT_SIG_IO  GPIO_NUM_5

// WiFi credentials
#define WIFI_SSID "LF3000"
#define WIFI_PASS "LF3000password"
#define WIFI_HOSTNAME "LF3000"

// Default values for frequency and angles
#define DEFAULT_MIN_FREQ 2100.0
#define DEFAULT_MAX_FREQ 4000.0
#define DEFAULT_MIN_ANGLE -45.0 // Starboard
#define DEFAULT_MAX_ANGLE 45.0  // Port

static int64_t last_count = 0;
static int64_t last_time = 0;
static double min_freq = 0.0, max_freq = 0.0, min_angle = 0.0, max_angle = 0.0;
static double current_freq = 0.0;
static double current_angle = 0.0;

void set_hostname(const char* hostname) {
    esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
    if (netif) {
        esp_err_t err = esp_netif_set_hostname(netif, hostname);
        if (err != ESP_OK) {
            ESP_LOGE("HOSTNAME", "Failed to set hostname: %s", esp_err_to_name(err));
        } else {
            ESP_LOGI("HOSTNAME", "Hostname set to: %s", hostname);
        }
    } else {
        ESP_LOGE("HOSTNAME", "Failed to get netif");
    }
}

// Helper function to convert double to int64_t for NVS storage and vice versa
static int64_t double_to_int64(double value) {
    return (int64_t)(value * 1e6); // Multiply by 1e6 to keep precision
}

static double int64_to_double(int64_t value) {
    return (double)value / 1e6; // Divide by 1e6 to get the original double value
}

static void load_min_max_freq_and_angle(void) {
    nvs_handle_t nvs_handle;
    esp_err_t err;
    int64_t stored_min_freq, stored_max_freq, stored_min_angle, stored_max_angle;

    // Initialize NVS
    err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        // NVS partition was truncated and needs to be erased
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK(err);

    // Open
    err = nvs_open("storage", NVS_READWRITE, &nvs_handle);
    if (err != ESP_OK) {
        printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));
        return;
    }

    // Read or write default values for frequency
    if (nvs_get_i64(nvs_handle, "min_freq", &stored_min_freq) != ESP_OK) {
        stored_min_freq = double_to_int64(DEFAULT_MIN_FREQ);
        nvs_set_i64(nvs_handle, "min_freq", stored_min_freq);
    }
    if (nvs_get_i64(nvs_handle, "max_freq", &stored_max_freq) != ESP_OK) {
        stored_max_freq = double_to_int64(DEFAULT_MAX_FREQ);
        nvs_set_i64(nvs_handle, "max_freq", stored_max_freq);
    }

    // Read or write default values for angles
    if (nvs_get_i64(nvs_handle, "min_angle", &stored_min_angle) != ESP_OK) {
        stored_min_angle = double_to_int64(DEFAULT_MIN_ANGLE);
        nvs_set_i64(nvs_handle, "min_angle", stored_min_angle);
    }
    if (nvs_get_i64(nvs_handle, "max_angle", &stored_max_angle) != ESP_OK) {
        stored_max_angle = double_to_int64(DEFAULT_MAX_ANGLE);
        nvs_set_i64(nvs_handle, "max_angle", stored_max_angle);
    }

    // Convert stored int64_t values back to doubles
    min_freq = int64_to_double(stored_min_freq);
    max_freq = int64_to_double(stored_max_freq);
    min_angle = int64_to_double(stored_min_angle);
    max_angle = int64_to_double(stored_max_angle);

    // Commit written value.
    err = nvs_commit(nvs_handle);
    if (err != ESP_OK) {
        printf("Error (%s) committing NVS!\n", esp_err_to_name(err));
    }

    // Close
    nvs_close(nvs_handle);
}

void save_new_values(double new_min_freq, double new_max_freq, double new_min_angle, double new_max_angle) {
    nvs_handle_t nvs_handle;
    esp_err_t err;

    // Initialize NVS
    err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        // NVS partition was truncated and needs to be erased
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK(err);

    // Open
    err = nvs_open("storage", NVS_READWRITE, &nvs_handle);
    if (err != ESP_OK) {
        printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));
        return;
    }

    // Save new values
    err = nvs_set_i64(nvs_handle, "min_freq", double_to_int64(new_min_freq));
    if (err != ESP_OK) {
        printf("Error (%s) setting min_freq!\n", esp_err_to_name(err));
    }
    err = nvs_set_i64(nvs_handle, "max_freq", double_to_int64(new_max_freq));
    if (err != ESP_OK) {
        printf("Error (%s) setting max_freq!\n", esp_err_to_name(err));
    }
    err = nvs_set_i64(nvs_handle, "min_angle", double_to_int64(new_min_angle));
    if (err != ESP_OK) {
        printf("Error (%s) setting min_angle!\n", esp_err_to_name(err));
    }
    err = nvs_set_i64(nvs_handle, "max_angle", double_to_int64(new_max_angle));
    if (err != ESP_OK) {
        printf("Error (%s) setting max_angle!\n", esp_err_to_name(err));
    }

    // Commit written values
    err = nvs_commit(nvs_handle);
    if (err != ESP_OK) {
        printf("Error (%s) committing NVS!\n", esp_err_to_name(err));
    }

    // Close
    nvs_close(nvs_handle);
}

void uart_init(void)
{
    // Configure UART for serial output at 9600 baud
    uart_config_t uart_config = {
        .baud_rate = 9600,
        .data_bits = UART_DATA_8_BITS,
        .parity    = UART_PARITY_DISABLE,
        .stop_bits = UART_STOP_BITS_1,
        .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
        .source_clk = UART_SCLK_APB,
    };
    uart_param_config(UART_NUM_0, &uart_config);
    uart_driver_install(UART_NUM_0, 1024 * 2, 0, 0, NULL, 0);
}

void pcnt_example_init(void)
{
    pcnt_config_t pcnt_config = {
        .pulse_gpio_num = PCNT_INPUT_SIG_IO,
        .ctrl_gpio_num = -1, // No control GPIO defined
        .lctrl_mode = PCNT_MODE_KEEP,
        .hctrl_mode = PCNT_MODE_KEEP,
        .pos_mode = PCNT_COUNT_INC,
        .neg_mode = PCNT_COUNT_DIS,
        .counter_h_lim = 65535,
        .counter_l_lim = 0,
        .unit = PCNT_UNIT,
        .channel = PCNT_CHANNEL_0,
    };

    // Initialize PCNT unit
    pcnt_unit_config(&pcnt_config);

    // Clear PCNT unit before enabling
    pcnt_counter_clear(PCNT_UNIT);

    // Enable PCNT unit
    pcnt_counter_resume(PCNT_UNIT);
}

static void wifi_init_ap(void) {
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_t *ap_netif = esp_netif_create_default_wifi_ap();
    assert(ap_netif);

    // Configure static IP address
    //esp_netif_ip_info_t ip_info;
    //IP4_ADDR(&ip_info.ip, 192, 168, 1, 1);  // Set the IP address
    //IP4_ADDR(&ip_info.gw, 192, 168, 1, 1);  // Gateway address, same as IP for AP
    //IP4_ADDR(&ip_info.netmask, 255, 255, 255, 0); // Netmask

    esp_netif_dhcps_stop(ap_netif);  // Stop DHCP server before setting static IP
    //esp_netif_set_ip_info(ap_netif, &ip_info);
    esp_netif_dhcps_start(ap_netif);  // Restart DHCP server with new settings

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    wifi_config_t wifi_config = {
        .ap = {
            .ssid = WIFI_SSID,
            .ssid_len = strlen(WIFI_SSID),
            .channel = 1,
            .password = WIFI_PASS,
            .max_connection = 4,
            .authmode = WIFI_AUTH_WPA_WPA2_PSK
        },
    };

    if (strlen(WIFI_PASS) == 0) {
        wifi_config.ap.authmode = WIFI_AUTH_OPEN;
    }

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    set_hostname(WIFI_HOSTNAME);

    ESP_LOGI("WIFI", "wifi_init_ap finished. SSID:%s password:%s",
             WIFI_SSID, WIFI_PASS);
}

/* An HTTP GET handler */
static esp_err_t root_get_handler(httpd_req_t *req)
{
    char*  buf;
    size_t buf_len;

    buf_len = httpd_req_get_url_query_len(req) + 1;
    if (buf_len > 1) {
        buf = malloc(buf_len);
        if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
            char param[32];
            if (httpd_query_key_value(buf, "min_freq", param, sizeof(param)) == ESP_OK) {
                min_freq = atof(param);
            }
            if (httpd_query_key_value(buf, "max_freq", param, sizeof(param)) == ESP_OK) {
                max_freq = atof(param);
            }
            if (httpd_query_key_value(buf, "min_angle", param, sizeof(param)) == ESP_OK) {
                min_angle = atof(param);
            }
            if (httpd_query_key_value(buf, "max_angle", param, sizeof(param)) == ESP_OK) {
                max_angle = atof(param);
            }
            save_new_values(min_freq, max_freq, min_angle, max_angle);
        }
        free(buf);
    }

    char response[512];
    snprintf(response, sizeof(response),
        "<html><body>"
        "<h1>Rudder Control</h1>"
        "<p>Current Frequency: %.2f Hz</p>"
        "<p>Current Rudder Angle: %.2f degrees</p>"
        "<form action='/'>"
        "Min Frequency: <input type='text' name='min_freq' value='%.2f'><br>"
        "Max Frequency: <input type='text' name='max_freq' value='%.2f'><br>"
        "Min Angle: <input type='text' name='min_angle' value='%.2f'><br>"
        "Max Angle: <input type='text' name='max_angle' value='%.2f'><br>"
        "<input type='submit' value='Update Settings'>"
        "</form></body></html>",
        current_freq, current_angle, min_freq, max_freq, min_angle, max_angle);

    httpd_resp_send(req, response, strlen(response));
    return ESP_OK;
}

httpd_uri_t root = {
    .uri       = "/",
    .method    = HTTP_GET,
    .handler   = root_get_handler
};

httpd_handle_t start_webserver(void) {
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();
    httpd_handle_t server = NULL;

    if (httpd_start(&server, &config) == ESP_OK) {
        httpd_register_uri_handler(server, &root);
    }
    return server;
}

void loop() {
    int16_t count = 0;
    pcnt_get_counter_value(PCNT_UNIT, &count);
    int64_t current_time = esp_timer_get_time();

    if(last_time != 0) {
        int64_t delta_count = count - last_count;
        int64_t delta_time = current_time - last_time; // delta in microseconds
        if(delta_count != 0) {
            current_freq = (double)delta_count / (delta_time / 1000000.0); // convert to Hz
            if (current_freq > 0) {
                // Convert frequency to rudder angle using loaded or default values
                if (current_freq <= min_freq) {
                    current_angle = min_angle; // Minimum frequency corresponds to the min angle (starboard)
                } else if (current_freq >= max_freq) {
                    current_angle = max_angle; // Maximum frequency corresponds to the max angle (port)
                } else {
                    // Linear interpolation for angles between min_angle and max_angle
                    current_angle = min_angle + (max_angle - min_angle) * (current_freq - min_freq) / (max_freq - min_freq);
                }
                printf("Frequency: %.2f Hz, Rudder Angle: %.2f degrees\n", current_freq, current_angle);

            }
        } else {
            printf("Frequency: 0 Hz, Rudder Angle: %.2f degrees\n", (min_angle + max_angle) / 2.0); // Middle position when frequency is 0
            current_freq = 0.0;
            current_angle = (min_angle + max_angle) / 2.0; // Middle position when frequency is 0
        }
    }

    last_count = count;
    last_time = current_time;

    vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait for 1 second
}

void app_main(void)
{
    uart_init();
    load_min_max_freq_and_angle();
    pcnt_example_init();
    wifi_init_ap();
    httpd_handle_t server = start_webserver();

    while(1) {
        loop();
    }
}

Looks great!
On Thu, Dec 5, 2024 at 23:59 Trevor Flynn @.***> wrote:

@fgorina https://github.com/fgorina OK I've made progress on this.
This is my current wiring diagram. I am getting very good results with
this on the LR3000.
image.png (view on web)
https://github.com/user-attachments/assets/0a420fa6-8284-47d6-a4bd-ea78efbb1c9b
I only used a single input filter, I am not using a second stage filter
between the op amp and the opto isolator. Perhaps it could use one, but I
am not seeing any issues right now. The load on the R1 resistor is about
31mA. I've decided to use a 1 watt resistor for it (.03a * 12v = 0.36watts)
I'm not hitting close to 1 watt, but I am noticing that my smaller
resistors were getting hot, and the 1 watt resistor is not getting as warm.
As expected on the LR3000+/- terminals, they seem to be interchangeable
with no difference in the signal. Most of the signal distortion is caused
by the first stage filter. A better sized filter would help a lot with
getting a cleaner signal, but the opto-isolator makes a nice square wave
and does not really care about how noisy the signal is leaving the op amp,
so the only thing that it would do is get a more symmetric duty cycle out
of the opto isolator. But in this application, we do not care about the
duty cycle, only the frequency, so it does not matter. The first stage
filter is doing a great job at removing the DC component of the signal and
that is all that matters.
This is my current code I am using on the ESP32 for getting the rudder
angle. I'm getting solid results. My next task will be to add NMEA 2000
support to the code. I threw a webserver onto it so I can calibrate it once
I have the final circuit installed on the boat. Since I am using an off the
shelf autopilot, the NMEA2000 interface will be the next required task. I
have ordered some CAN32 boards and will test the code on them next.
#include <stdio.h>#include "freertos/FreeRTOS.h"#include "freertos/task.h"#include "driver/pcnt.h"#include "esp_system.h"#include "esp_timer.h"#include "driver/uart.h"#include "driver/gpio.h"#include "nvs_flash.h"#include "nvs.h"#include "esp_wifi.h"#include "esp_event.h"#include "esp_log.h"#include "esp_http_server.h"#include "string.h"#include "esp_netif.h"#include "esp_wifi_types.h"
#define PCNT_UNIT      PCNT_UNIT_0#define PCNT_INPUT_SIG_IO  GPIO_NUM_5
// WiFi credentials#define WIFI_SSID "LF3000"#define WIFI_PASS "LF3000password"#define WIFI_HOSTNAME "LF3000"
// Default values for frequency and angles#define DEFAULT_MIN_FREQ 2100.0#define DEFAULT_MAX_FREQ 4000.0#define DEFAULT_MIN_ANGLE -45.0 // Starboard#define DEFAULT_MAX_ANGLE 45.0  // Port
static int64_t last_count = 0;static int64_t last_time = 0;static double min_freq = 0.0, max_freq = 0.0, min_angle = 0.0, max_angle = 0.0;static double current_freq = 0.0;static double current_angle = 0.0;
void set_hostname(const char* hostname) {
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (netif) {
esp_err_t err = esp_netif_set_hostname(netif, hostname);
if (err != ESP_OK) {
ESP_LOGE("HOSTNAME", "Failed to set hostname: %s", esp_err_to_name(err));
} else {
ESP_LOGI("HOSTNAME", "Hostname set to: %s", hostname);
}
} else {
ESP_LOGE("HOSTNAME", "Failed to get netif");
}
}
// Helper function to convert double to int64_t for NVS storage and vice versastatic int64_t double_to_int64(double value) {
return (int64_t)(value * 1e6); // Multiply by 1e6 to keep precision
}
static double int64_to_double(int64_t value) {
return (double)value / 1e6; // Divide by 1e6 to get the original double value
}
static void load_min_max_freq_and_angle(void) {
nvs_handle_t nvs_handle;
esp_err_t err;
int64_t stored_min_freq, stored_max_freq, stored_min_angle, stored_max_angle;
// Initialize NVS
err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
    // NVS partition was truncated and needs to be erased
    ESP_ERROR_CHECK(nvs_flash_erase());
    err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);

// Open
err = nvs_open("storage", NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
    printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));
    return;
}

// Read or write default values for frequency
if (nvs_get_i64(nvs_handle, "min_freq", &stored_min_freq) != ESP_OK) {
    stored_min_freq = double_to_int64(DEFAULT_MIN_FREQ);
    nvs_set_i64(nvs_handle, "min_freq", stored_min_freq);
}
if (nvs_get_i64(nvs_handle, "max_freq", &stored_max_freq) != ESP_OK) {
    stored_max_freq = double_to_int64(DEFAULT_MAX_FREQ);
    nvs_set_i64(nvs_handle, "max_freq", stored_max_freq);
}

// Read or write default values for angles
if (nvs_get_i64(nvs_handle, "min_angle", &stored_min_angle) != ESP_OK) {
    stored_min_angle = double_to_int64(DEFAULT_MIN_ANGLE);
    nvs_set_i64(nvs_handle, "min_angle", stored_min_angle);
}
if (nvs_get_i64(nvs_handle, "max_angle", &stored_max_angle) != ESP_OK) {
    stored_max_angle = double_to_int64(DEFAULT_MAX_ANGLE);
    nvs_set_i64(nvs_handle, "max_angle", stored_max_angle);
}

// Convert stored int64_t values back to doubles
min_freq = int64_to_double(stored_min_freq);
max_freq = int64_to_double(stored_max_freq);
min_angle = int64_to_double(stored_min_angle);
max_angle = int64_to_double(stored_max_angle);

// Commit written value.
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
    printf("Error (%s) committing NVS!\n", esp_err_to_name(err));
}

// Close
nvs_close(nvs_handle);

}
void save_new_values(double new_min_freq, double new_max_freq, double new_min_angle, double new_max_angle) {
nvs_handle_t nvs_handle;
esp_err_t err;
// Initialize NVS
err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
    // NVS partition was truncated and needs to be erased
    ESP_ERROR_CHECK(nvs_flash_erase());
    err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);

// Open
err = nvs_open("storage", NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
    printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));
    return;
}

// Save new values
err = nvs_set_i64(nvs_handle, "min_freq", double_to_int64(new_min_freq));
if (err != ESP_OK) {
    printf("Error (%s) setting min_freq!\n", esp_err_to_name(err));
}
err = nvs_set_i64(nvs_handle, "max_freq", double_to_int64(new_max_freq));
if (err != ESP_OK) {
    printf("Error (%s) setting max_freq!\n", esp_err_to_name(err));
}
err = nvs_set_i64(nvs_handle, "min_angle", double_to_int64(new_min_angle));
if (err != ESP_OK) {
    printf("Error (%s) setting min_angle!\n", esp_err_to_name(err));
}
err = nvs_set_i64(nvs_handle, "max_angle", double_to_int64(new_max_angle));
if (err != ESP_OK) {
    printf("Error (%s) setting max_angle!\n", esp_err_to_name(err));
}

// Commit written values
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
    printf("Error (%s) committing NVS!\n", esp_err_to_name(err));
}

// Close
nvs_close(nvs_handle);

}
void uart_init(void)
{
// Configure UART for serial output at 9600 baud
uart_config_t uart_config = {
.baud_rate = 9600,
.data_bits = UART_DATA_8_BITS,
.parity    = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_APB,
};
uart_param_config(UART_NUM_0, &uart_config);
uart_driver_install(UART_NUM_0, 1024 * 2, 0, 0, NULL, 0);
}
void pcnt_example_init(void)
{
pcnt_config_t pcnt_config = {
.pulse_gpio_num = PCNT_INPUT_SIG_IO,
.ctrl_gpio_num = -1, // No control GPIO defined
.lctrl_mode = PCNT_MODE_KEEP,
.hctrl_mode = PCNT_MODE_KEEP,
.pos_mode = PCNT_COUNT_INC,
.neg_mode = PCNT_COUNT_DIS,
.counter_h_lim = 65535,
.counter_l_lim = 0,
.unit = PCNT_UNIT,
.channel = PCNT_CHANNEL_0,
};
// Initialize PCNT unit
pcnt_unit_config(&pcnt_config);

// Clear PCNT unit before enabling
pcnt_counter_clear(PCNT_UNIT);

// Enable PCNT unit
pcnt_counter_resume(PCNT_UNIT);

}
static void wifi_init_ap(void) {
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_t *ap_netif = esp_netif_create_default_wifi_ap();
assert(ap_netif);

// Configure static IP address
//esp_netif_ip_info_t ip_info;
//IP4_ADDR(&ip_info.ip, 192, 168, 1, 1);  // Set the IP address
//IP4_ADDR(&ip_info.gw, 192, 168, 1, 1);  // Gateway address, same as IP for AP
//IP4_ADDR(&ip_info.netmask, 255, 255, 255, 0); // Netmask

esp_netif_dhcps_stop(ap_netif);  // Stop DHCP server before setting static IP
//esp_netif_set_ip_info(ap_netif, &ip_info);
esp_netif_dhcps_start(ap_netif);  // Restart DHCP server with new settings

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));

wifi_config_t wifi_config = {
    .ap = {
        .ssid = WIFI_SSID,
        .ssid_len = strlen(WIFI_SSID),
        .channel = 1,
        .password = WIFI_PASS,
        .max_connection = 4,
        .authmode = WIFI_AUTH_WPA_WPA2_PSK
    },
};

if (strlen(WIFI_PASS) == 0) {
    wifi_config.ap.authmode = WIFI_AUTH_OPEN;
}

ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());

set_hostname(WIFI_HOSTNAME);

ESP_LOGI("WIFI", "wifi_init_ap finished. SSID:%s password:%s",
         WIFI_SSID, WIFI_PASS);

}
/* An HTTP GET handler */static esp_err_t root_get_handler(httpd_req_t req)
{
char  buf;
size_t buf_len;
buf_len = httpd_req_get_url_query_len(req) + 1;
if (buf_len > 1) {
    buf = malloc(buf_len);
    if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
        char param[32];
        if (httpd_query_key_value(buf, "min_freq", param, sizeof(param)) == ESP_OK) {
            min_freq = atof(param);
        }
        if (httpd_query_key_value(buf, "max_freq", param, sizeof(param)) == ESP_OK) {
            max_freq = atof(param);
        }
        if (httpd_query_key_value(buf, "min_angle", param, sizeof(param)) == ESP_OK) {
            min_angle = atof(param);
        }
        if (httpd_query_key_value(buf, "max_angle", param, sizeof(param)) == ESP_OK) {
            max_angle = atof(param);
        }
        save_new_values(min_freq, max_freq, min_angle, max_angle);
    }
    free(buf);
}

char response[512];
snprintf(response, sizeof(response),
    "<html><body>"
    "<h1>Rudder Control</h1>"
    "<p>Current Frequency: %.2f Hz</p>"
    "<p>Current Rudder Angle: %.2f degrees</p>"
    "<form action='/'>"
    "Min Frequency: <input type='text' name='min_freq' value='%.2f'><br>"
    "Max Frequency: <input type='text' name='max_freq' value='%.2f'><br>"
    "Min Angle: <input type='text' name='min_angle' value='%.2f'><br>"
    "Max Angle: <input type='text' name='max_angle' value='%.2f'><br>"
    "<input type='submit' value='Update Settings'>"
    "</form></body></html>",
    current_freq, current_angle, min_freq, max_freq, min_angle, max_angle);

httpd_resp_send(req, response, strlen(response));
return ESP_OK;

}
httpd_uri_t root = {
.uri       = "/",
.method    = HTTP_GET,
.handler   = root_get_handler
};
httpd_handle_t start_webserver(void) {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_handle_t server = NULL;
if (httpd_start(&server, &config) == ESP_OK) {
    httpd_register_uri_handler(server, &root);
}
return server;

}
void loop() {
int16_t count = 0;
pcnt_get_counter_value(PCNT_UNIT, &count);
int64_t current_time = esp_timer_get_time();
if(last_time != 0) {
    int64_t delta_count = count - last_count;
    int64_t delta_time = current_time - last_time; // delta in microseconds
    if(delta_count != 0) {
        current_freq = (double)delta_count / (delta_time / 1000000.0); // convert to Hz
        if (current_freq > 0) {
            // Convert frequency to rudder angle using loaded or default values
            if (current_freq <= min_freq) {
                current_angle = min_angle; // Minimum frequency corresponds to the min angle (starboard)
            } else if (current_freq >= max_freq) {
                current_angle = max_angle; // Maximum frequency corresponds to the max angle (port)
            } else {
                // Linear interpolation for angles between min_angle and max_angle
                current_angle = min_angle + (max_angle - min_angle) * (current_freq - min_freq) / (max_freq - min_freq);
            }
            printf("Frequency: %.2f Hz, Rudder Angle: %.2f degrees\n", current_freq, current_angle);

        }
    } else {
        printf("Frequency: 0 Hz, Rudder Angle: %.2f degrees\n", (min_angle + max_angle) / 2.0); // Middle position when frequency is 0
        current_freq = 0.0;
        current_angle = (min_angle + max_angle) / 2.0; // Middle position when frequency is 0
    }
}

last_count = count;
last_time = current_time;

vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait for 1 second

}
void app_main(void)
{
uart_init();
load_min_max_freq_and_angle();
pcnt_example_init();
wifi_init_ap();
httpd_handle_t server = start_webserver();
while(1) {
    loop();
}

}
—
Reply to this email directly, view it on GitHub
https://github.com/fgorina/RF300-Converter/issues/1#issuecomment-2521655639,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AACXYUZOYIAIMSV45JNY3QL2EDLEZAVCNFSM6AAAAABPCDWMWKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDKMRRGY2TKNRTHE
.
You are receiving this because you were mentioned.Message ID:
@.***>

I had to convert the output to a voltage.
On Thu, Dec 5, 2024 at 23:59 Trevor Flynn @.***> wrote:

@fgorina https://github.com/fgorina OK I've made progress on this.
This is my current wiring diagram. I am getting very good results with
this on the LR3000.
image.png (view on web)
https://github.com/user-attachments/assets/0a420fa6-8284-47d6-a4bd-ea78efbb1c9b
I only used a single input filter, I am not using a second stage filter
between the op amp and the opto isolator. Perhaps it could use one, but I
am not seeing any issues right now. The load on the R1 resistor is about
31mA. I've decided to use a 1 watt resistor for it (.03a * 12v = 0.36watts)
I'm not hitting close to 1 watt, but I am noticing that my smaller
resistors were getting hot, and the 1 watt resistor is not getting as warm.
As expected on the LR3000+/- terminals, they seem to be interchangeable
with no difference in the signal. Most of the signal distortion is caused
by the first stage filter. A better sized filter would help a lot with
getting a cleaner signal, but the opto-isolator makes a nice square wave
and does not really care about how noisy the signal is leaving the op amp,
so the only thing that it would do is get a more symmetric duty cycle out
of the opto isolator. But in this application, we do not care about the
duty cycle, only the frequency, so it does not matter. The first stage
filter is doing a great job at removing the DC component of the signal and
that is all that matters.
This is my current code I am using on the ESP32 for getting the rudder
angle. I'm getting solid results. My next task will be to add NMEA 2000
support to the code. I threw a webserver onto it so I can calibrate it once
I have the final circuit installed on the boat. Since I am using an off the
shelf autopilot, the NMEA2000 interface will be the next required task. I
have ordered some CAN32 boards and will test the code on them next.
#include <stdio.h>#include "freertos/FreeRTOS.h"#include "freertos/task.h"#include "driver/pcnt.h"#include "esp_system.h"#include "esp_timer.h"#include "driver/uart.h"#include "driver/gpio.h"#include "nvs_flash.h"#include "nvs.h"#include "esp_wifi.h"#include "esp_event.h"#include "esp_log.h"#include "esp_http_server.h"#include "string.h"#include "esp_netif.h"#include "esp_wifi_types.h"
#define PCNT_UNIT      PCNT_UNIT_0#define PCNT_INPUT_SIG_IO  GPIO_NUM_5
// WiFi credentials#define WIFI_SSID "LF3000"#define WIFI_PASS "LF3000password"#define WIFI_HOSTNAME "LF3000"
// Default values for frequency and angles#define DEFAULT_MIN_FREQ 2100.0#define DEFAULT_MAX_FREQ 4000.0#define DEFAULT_MIN_ANGLE -45.0 // Starboard#define DEFAULT_MAX_ANGLE 45.0  // Port
static int64_t last_count = 0;static int64_t last_time = 0;static double min_freq = 0.0, max_freq = 0.0, min_angle = 0.0, max_angle = 0.0;static double current_freq = 0.0;static double current_angle = 0.0;
void set_hostname(const char* hostname) {
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
if (netif) {
esp_err_t err = esp_netif_set_hostname(netif, hostname);
if (err != ESP_OK) {
ESP_LOGE("HOSTNAME", "Failed to set hostname: %s", esp_err_to_name(err));
} else {
ESP_LOGI("HOSTNAME", "Hostname set to: %s", hostname);
}
} else {
ESP_LOGE("HOSTNAME", "Failed to get netif");
}
}
// Helper function to convert double to int64_t for NVS storage and vice versastatic int64_t double_to_int64(double value) {
return (int64_t)(value * 1e6); // Multiply by 1e6 to keep precision
}
static double int64_to_double(int64_t value) {
return (double)value / 1e6; // Divide by 1e6 to get the original double value
}
static void load_min_max_freq_and_angle(void) {
nvs_handle_t nvs_handle;
esp_err_t err;
int64_t stored_min_freq, stored_max_freq, stored_min_angle, stored_max_angle;
// Initialize NVS
err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
    // NVS partition was truncated and needs to be erased
    ESP_ERROR_CHECK(nvs_flash_erase());
    err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);

// Open
err = nvs_open("storage", NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
    printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));
    return;
}

// Read or write default values for frequency
if (nvs_get_i64(nvs_handle, "min_freq", &stored_min_freq) != ESP_OK) {
    stored_min_freq = double_to_int64(DEFAULT_MIN_FREQ);
    nvs_set_i64(nvs_handle, "min_freq", stored_min_freq);
}
if (nvs_get_i64(nvs_handle, "max_freq", &stored_max_freq) != ESP_OK) {
    stored_max_freq = double_to_int64(DEFAULT_MAX_FREQ);
    nvs_set_i64(nvs_handle, "max_freq", stored_max_freq);
}

// Read or write default values for angles
if (nvs_get_i64(nvs_handle, "min_angle", &stored_min_angle) != ESP_OK) {
    stored_min_angle = double_to_int64(DEFAULT_MIN_ANGLE);
    nvs_set_i64(nvs_handle, "min_angle", stored_min_angle);
}
if (nvs_get_i64(nvs_handle, "max_angle", &stored_max_angle) != ESP_OK) {
    stored_max_angle = double_to_int64(DEFAULT_MAX_ANGLE);
    nvs_set_i64(nvs_handle, "max_angle", stored_max_angle);
}

// Convert stored int64_t values back to doubles
min_freq = int64_to_double(stored_min_freq);
max_freq = int64_to_double(stored_max_freq);
min_angle = int64_to_double(stored_min_angle);
max_angle = int64_to_double(stored_max_angle);

// Commit written value.
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
    printf("Error (%s) committing NVS!\n", esp_err_to_name(err));
}

// Close
nvs_close(nvs_handle);

}
void save_new_values(double new_min_freq, double new_max_freq, double new_min_angle, double new_max_angle) {
nvs_handle_t nvs_handle;
esp_err_t err;
// Initialize NVS
err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
    // NVS partition was truncated and needs to be erased
    ESP_ERROR_CHECK(nvs_flash_erase());
    err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);

// Open
err = nvs_open("storage", NVS_READWRITE, &nvs_handle);
if (err != ESP_OK) {
    printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));
    return;
}

// Save new values
err = nvs_set_i64(nvs_handle, "min_freq", double_to_int64(new_min_freq));
if (err != ESP_OK) {
    printf("Error (%s) setting min_freq!\n", esp_err_to_name(err));
}
err = nvs_set_i64(nvs_handle, "max_freq", double_to_int64(new_max_freq));
if (err != ESP_OK) {
    printf("Error (%s) setting max_freq!\n", esp_err_to_name(err));
}
err = nvs_set_i64(nvs_handle, "min_angle", double_to_int64(new_min_angle));
if (err != ESP_OK) {
    printf("Error (%s) setting min_angle!\n", esp_err_to_name(err));
}
err = nvs_set_i64(nvs_handle, "max_angle", double_to_int64(new_max_angle));
if (err != ESP_OK) {
    printf("Error (%s) setting max_angle!\n", esp_err_to_name(err));
}

// Commit written values
err = nvs_commit(nvs_handle);
if (err != ESP_OK) {
    printf("Error (%s) committing NVS!\n", esp_err_to_name(err));
}

// Close
nvs_close(nvs_handle);

}
void uart_init(void)
{
// Configure UART for serial output at 9600 baud
uart_config_t uart_config = {
.baud_rate = 9600,
.data_bits = UART_DATA_8_BITS,
.parity    = UART_PARITY_DISABLE,
.stop_bits = UART_STOP_BITS_1,
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
.source_clk = UART_SCLK_APB,
};
uart_param_config(UART_NUM_0, &uart_config);
uart_driver_install(UART_NUM_0, 1024 * 2, 0, 0, NULL, 0);
}
void pcnt_example_init(void)
{
pcnt_config_t pcnt_config = {
.pulse_gpio_num = PCNT_INPUT_SIG_IO,
.ctrl_gpio_num = -1, // No control GPIO defined
.lctrl_mode = PCNT_MODE_KEEP,
.hctrl_mode = PCNT_MODE_KEEP,
.pos_mode = PCNT_COUNT_INC,
.neg_mode = PCNT_COUNT_DIS,
.counter_h_lim = 65535,
.counter_l_lim = 0,
.unit = PCNT_UNIT,
.channel = PCNT_CHANNEL_0,
};
// Initialize PCNT unit
pcnt_unit_config(&pcnt_config);

// Clear PCNT unit before enabling
pcnt_counter_clear(PCNT_UNIT);

// Enable PCNT unit
pcnt_counter_resume(PCNT_UNIT);

}
static void wifi_init_ap(void) {
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_t *ap_netif = esp_netif_create_default_wifi_ap();
assert(ap_netif);

// Configure static IP address
//esp_netif_ip_info_t ip_info;
//IP4_ADDR(&ip_info.ip, 192, 168, 1, 1);  // Set the IP address
//IP4_ADDR(&ip_info.gw, 192, 168, 1, 1);  // Gateway address, same as IP for AP
//IP4_ADDR(&ip_info.netmask, 255, 255, 255, 0); // Netmask

esp_netif_dhcps_stop(ap_netif);  // Stop DHCP server before setting static IP
//esp_netif_set_ip_info(ap_netif, &ip_info);
esp_netif_dhcps_start(ap_netif);  // Restart DHCP server with new settings

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));

wifi_config_t wifi_config = {
    .ap = {
        .ssid = WIFI_SSID,
        .ssid_len = strlen(WIFI_SSID),
        .channel = 1,
        .password = WIFI_PASS,
        .max_connection = 4,
        .authmode = WIFI_AUTH_WPA_WPA2_PSK
    },
};

if (strlen(WIFI_PASS) == 0) {
    wifi_config.ap.authmode = WIFI_AUTH_OPEN;
}

ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());

set_hostname(WIFI_HOSTNAME);

ESP_LOGI("WIFI", "wifi_init_ap finished. SSID:%s password:%s",
         WIFI_SSID, WIFI_PASS);

}
/* An HTTP GET handler */static esp_err_t root_get_handler(httpd_req_t req)
{
char  buf;
size_t buf_len;
buf_len = httpd_req_get_url_query_len(req) + 1;
if (buf_len > 1) {
    buf = malloc(buf_len);
    if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
        char param[32];
        if (httpd_query_key_value(buf, "min_freq", param, sizeof(param)) == ESP_OK) {
            min_freq = atof(param);
        }
        if (httpd_query_key_value(buf, "max_freq", param, sizeof(param)) == ESP_OK) {
            max_freq = atof(param);
        }
        if (httpd_query_key_value(buf, "min_angle", param, sizeof(param)) == ESP_OK) {
            min_angle = atof(param);
        }
        if (httpd_query_key_value(buf, "max_angle", param, sizeof(param)) == ESP_OK) {
            max_angle = atof(param);
        }
        save_new_values(min_freq, max_freq, min_angle, max_angle);
    }
    free(buf);
}

char response[512];
snprintf(response, sizeof(response),
    "<html><body>"
    "<h1>Rudder Control</h1>"
    "<p>Current Frequency: %.2f Hz</p>"
    "<p>Current Rudder Angle: %.2f degrees</p>"
    "<form action='/'>"
    "Min Frequency: <input type='text' name='min_freq' value='%.2f'><br>"
    "Max Frequency: <input type='text' name='max_freq' value='%.2f'><br>"
    "Min Angle: <input type='text' name='min_angle' value='%.2f'><br>"
    "Max Angle: <input type='text' name='max_angle' value='%.2f'><br>"
    "<input type='submit' value='Update Settings'>"
    "</form></body></html>",
    current_freq, current_angle, min_freq, max_freq, min_angle, max_angle);

httpd_resp_send(req, response, strlen(response));
return ESP_OK;

}
httpd_uri_t root = {
.uri       = "/",
.method    = HTTP_GET,
.handler   = root_get_handler
};
httpd_handle_t start_webserver(void) {
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_handle_t server = NULL;
if (httpd_start(&server, &config) == ESP_OK) {
    httpd_register_uri_handler(server, &root);
}
return server;

}
void loop() {
int16_t count = 0;
pcnt_get_counter_value(PCNT_UNIT, &count);
int64_t current_time = esp_timer_get_time();
if(last_time != 0) {
    int64_t delta_count = count - last_count;
    int64_t delta_time = current_time - last_time; // delta in microseconds
    if(delta_count != 0) {
        current_freq = (double)delta_count / (delta_time / 1000000.0); // convert to Hz
        if (current_freq > 0) {
            // Convert frequency to rudder angle using loaded or default values
            if (current_freq <= min_freq) {
                current_angle = min_angle; // Minimum frequency corresponds to the min angle (starboard)
            } else if (current_freq >= max_freq) {
                current_angle = max_angle; // Maximum frequency corresponds to the max angle (port)
            } else {
                // Linear interpolation for angles between min_angle and max_angle
                current_angle = min_angle + (max_angle - min_angle) * (current_freq - min_freq) / (max_freq - min_freq);
            }
            printf("Frequency: %.2f Hz, Rudder Angle: %.2f degrees\n", current_freq, current_angle);

        }
    } else {
        printf("Frequency: 0 Hz, Rudder Angle: %.2f degrees\n", (min_angle + max_angle) / 2.0); // Middle position when frequency is 0
        current_freq = 0.0;
        current_angle = (min_angle + max_angle) / 2.0; // Middle position when frequency is 0
    }
}

last_count = count;
last_time = current_time;

vTaskDelay(1000 / portTICK_PERIOD_MS); // Wait for 1 second

}
void app_main(void)
{
uart_init();
load_min_max_freq_and_angle();
pcnt_example_init();
wifi_init_ap();
httpd_handle_t server = start_webserver();
while(1) {
    loop();
}

}
—
Reply to this email directly, view it on GitHub
https://github.com/fgorina/RF300-Converter/issues/1#issuecomment-2521655639,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/AACXYUZOYIAIMSV45JNY3QL2EDLEZAVCNFSM6AAAAABPCDWMWKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDKMRRGY2TKNRTHE
.
You are receiving this because you were mentioned.Message ID:
@.***>

For R1, I have changed to a 220 Ohm resistor and am getting better results.

I guess I'll close this. Nothing keeping this open. Thanks for all the help!

Thanks

On Dec 15 2024, at 4:37 AM, Trevor Flynn  @.***>  wrote:

I guess I'll close this. Nothing keeping this open. Thanks for all the help!
—
Reply to this email directly,     view it on GitHub (https://github.com/fgorina/RF300-Converter/issues/1#issuecomment-2543432548), or     unsubscribe (https://github.com/notifications/unsubscribe-auth/AACXYU2AEHQ25GB6UXSBK7L2FT2N7AVCNFSM6AAAAABPCDWMWKVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDKNBTGQZTENJUHA).
You are receiving this because you were mentioned.


Blazor Hybrid Static Files topic

Fixes #25674
Internal Review Topic
Notes ...

Rather rough at the moment 🚧👷. I'll 🛌💤 on it tonight with a round of updates on Thursday morning. You can review it now tho if you want.
I put the component-only static asset web root approach (wwwroot) on here in a section at the end because I wonder if a Hybrid app that only uses components can rely upon the Blazor-y-ish™ way of hosting and referencing static assets, not getting into additional complexity with localized resources (i.e., Resource Manager). I was able to get it working on Windows for MAUI, WPF, and WinForms. Idk about perf, security, or platform/OS problems that might crop up with it. If InSaNe 😵, I'll pull it off the PR ... and we'll never mention that these RexHax™ ever happened! 😆
Later, I'm probably going to use pivots to shield readers from frameworks that they aren't working in.
These cross-references don't seem ideal, but they are on the PR for .NET MAUI ...

https://github.com/dotnet/maui/pull/4367 is a GH PR ... definitely not a good form of documentation.
https://docs.microsoft.com/dotnet/maui/fundamentals/single-project probably isn't updated with the latest bits that the PR addresses ... but IDK if that's correct, just guessing at this point.

Tanay to review first. After Tanay's signoff, I'll ping DR and Artak.

Improve consistency with resizetizer maui#4367 is a GH PR ... definitely not a good form of documentation.

💯 , I was hoping eventually we may get .NET MAUI docs covering MauiAsset. cc/ @mattleibow is anything planned in this area?

@davidbritch do we have docs on MauiAsset? I recall I saw some, but it may be in the review stuff still?

That's good if that's all you spotted. I'll take care of these updates on Friday morning, edit the whole thing again, and ping u back.

@guardrex The "Essentials" docs will be live on Monday. I can provide an URL if required.

Thanks @davidbritch ... Yes, please. I think we can hold this until Monday. It might not even be likely for Dan and/or Artak to see this by then anyway.

@guardrex There's no root URL for Essentials, because there's no concept of Essentials in MAUI. It's all just functionality that's in MAUI. Do you want to link to a specific doc?

The following lines refer to ".NET MAUI Essentials."

If the general phrasing is incorrect, let's fix it.
If ".NET MAUI Essentials" is retained anywhere here, we can link the text to something ... or not ... whatever makes sense.

btw - I'm mostly a web dev coming into desktop/mobile subjects. I'm learning as we go 👨🏫.

In a Blazor Hybrid app, static files are *app resources*, accessed by Razor components using the following approaches:

[.NET MAUI](#net-maui): [:::no-loc text=".NET MAUI Essentials":::](https://devblogs.microsoft.com/xamarin/xamarin-essentials-1-7-and-introducing-net-maui-essentials/#net-maui-essentials)
...

... and ...

In .NET MAUI apps, [raw assets](/dotnet/maui/fundamentals/single-project#raw-assets) using the `MauiAsset` build action and :::no-loc text=".NET MAUI Essentials"::: are used for static assets.

... and ...

In a Razor component:

The static text file contents can be read using the following techniques:

.NET MAUI: :::no-loc text=".NET MAUI Essentials"::: (Microsoft.Maui.Storage.FileSystem.OpenAppPackageFileAsync)
...

...

It might make sense to change ".NET MAUI Essentials" to ".NET MAUI framework" or similar lingo.

Yes, there's no concept of ".NET MAUI Essentials". We still call it Essentials internally, but it's no longer a brand, and the functionality that was in the Xamarin.Essentials namespace is now split over multiple Microsoft.Maui.* namespaces (e.g. Microsoft.Maui.Devices, Microsoft.Maui.ApplicationModel etc.). What was .NET MAUI Essentials is now just .NET MAUI.
The blog link can be replaced with /dotnet/maui/platform-integration/storage/file-system-helpers. If you have access to the review site you can see it at https://review.docs.microsoft.com/en-us/dotnet/maui/platform-integration/storage/file-system-helpers?branch=pr-en-us-536&tabs=android

Should I change the text (for that cross-link to ".NET MAUI File System Helpers"?

btw @davidbritch in case ur wondering about the no-loc entries ...
We've devised an approach to supply a global no-loc via fileMetadata in the docfx.json file. We haven't implemented it yet. For now, I'm going to lock them out of loc this way. Later, the no-loc markdown will be removed.

The cross-link to ...
https://docs.microsoft.com/dotnet/maui/platform-integration/storage/file-system-helpers
... is LIVE. Taking this off of its HOLD/DRAFT status.


Removed email from Mailgun's suppression list on unsubscribe

refs https://github.com/TryGhost/Product/issues/4075

when a member clicks on "Unsubscribe from that list" from Apple Mail, the member's email is put into Mailgun's Unsubscribe suppression list. Ghost listens for "Unsubscribe" events from Mailgun, and unsubscribes the member from all the newsletters
now, the member is only unsubscribed from the newsletter they unsubscribe to (not all of them)
now, the email is also deleted from Mailgun's suppression list, so that it doesn't affect any other membership

I do have a concern that popped up in my mind about the whole flow. Imagine the following situation:

Site A sends a newsletter to person A.
Person A unsubscribes via the mailto
Mailgun adds it to the global unsubscribe list
Site B sends a newsletter to person A
Email got blocked by Mailgun
Site A fetches unsubscribes and removes person A from Mailguns list (this can happen with a huge delay of hours because sometime we receive thousands and thousands of events that take a while before they are all processed)


Problem with python

Everything was fine for about a month. But yesterday it started giving out such an error. I don't understand programming. Please give me advice on how to fix it.

you missed the dependencies cell

нет, она показывает что все нормально.

dreambooth : https://colab.research.google.com/github/TheLastBen/fast-stable-diffusion/blob/main/fast-DreamBooth.ipynb
A1111 : https://colab.research.google.com/github/TheLastBen/fast-stable-diffusion/blob/main/fast_stable_diffusion_AUTOMATIC1111.ipynb
You are running an old colab, always use the link


unfork macro paradise

a little bit of poetry, that. "unfork macro paradise".
since the fork happened, it looks like Eugene B took care of making
the needed changes upstream.
fixes #302

@xeno-by I was a little puzzled that the needed changes were present in your 2.12.0-RC1 but not your 2.12.x, but I didn't investigate

test run: https://scala-ci.typesafe.com/view/scala-2.12.x/job/scala-2.12.x-integrate-community-build/672/consoleFull (404 til Jenkins catches up)

new test run: https://scala-ci.typesafe.com/view/scala-2.12.x/job/scala-2.12.x-integrate-community-build/678/consoleFull (404 til Jenkins catches up)


feat(tutorial): complete step 2

Closes #
{{short description}}
Changelog
New

{{new thing}}

Changed

{{change thing}}

Removed

{{removed thing}}

No updates in 30 days. Closing.


fixed width for content form

tasks:

[ ] test coverage
[ ] gather feedback for my changes
[ ] submit changes to the documentation
[x] fix content form width
[x] fix seo form width
[x] fix settings width
[x] fix excerpt width
[x] fix permission width

informations:

q
a

Fixed tickets

BC breaks

Documentation PR

@turbo-ele Can you test if this change also works for your contact sidebar? Also the responsive behavior.

@danrot works like a charm


Question about  data(alpha or trimp)

Thank you very much for making this project open source. I have a question and I hope to get your answer.
In the demo.py
We should provide two images:

image(put in data/fg_test),I temporarily call it 1.png
imges's alpha (put in data/mask_test),I temporarily call it 1_alpha.png

Does 1_alpha.png must be obtained manually by PS？
Is there any way to get it automatically？
So we only need to upload a picture of 1.png.

Yes, trimap is necessary in this settings.


test-branch/execute-job/quoted-authorization/9d6d8e39-0de9-47e5-97ce-f323985e9125

PR for testing 0de3b97131d0fb7797f327839f110966b8182e34

/canonical/self-hosted-runners/run-workflows 0de3b97131d0fb7797f327839f110966b8182e34


youre taking too long

i would have completed the new rust atp get faster young one

You're*

Thanks for pointing that out! I’m sure it’s a huge relief to know that you noticed. I'll definitely be more careful next time. 😌


Performance issue with version 5 of NR gem

Recently we upgraded to our newrelic gem from <IP_ADDRESS>3 to <IP_ADDRESS>7 we ran into a performance hit.

This seems to be related to the calls to the media type renderer. Bypassing the renderer removes the performance hit completely.

Glen,
That seems to be correct in 5.x We haven't had to change the gem in many years, and I am not quite familiar with the new changes in the agent.
I am happy to assist in getting this solved, but given my scarce time availability I cannot take this on at the moment.
Te workaround until that is in place is to disable the rendering instrumentation, like you suggested. For example:
ENV['NEW_RELIC_DISABLE_PRAXIS_BLUEPRINTS_INSTRUMENTATION'] = 'true'
or simply start the app with that env variable set.
Do you think you can help getting this resolved, or know somebody that can?


Emojis are not rendered correctly in the output pane

when running dotnet try on windows machine is important that content authors set the console encoding to utf8 for the fix to completely work.


typo: succesfully -> successfully

OPS Build status updates of commit 5b51fbc:
:clock10: Preparing: average preparing time is 3 min(s) 17 sec(s)

OPS Build status updates of commit 5b51fbc:
:clock10: Incremental building: average incremental building time is 54 min(s) 26 sec(s)

OPS Build status updates of commit 5b51fbc:
:white_check_mark: Validation status: passed

File
Status
Preview URL
Details

access/Concepts/Miscellaneous/concurrent-schema-changes-caused-the-create-replica-operation-to-fail-try-againe.md
:white_check_mark:Succeeded
View

api/Publisher.MailMergeDataField.UnMapRecipientField.md
:white_check_mark:Succeeded
View

For more details, please refer to the build report.
Note: If you changed an existing file name or deleted a file, broken links in other files to the deleted or renamed file are listed only in the full build report.


Action menu and maybe others outside the camera limits

If you open the action menu close to the edge, it's partly outside the camera limits or viewport or whatever.
All HUD elements should also be clamped within the view.
Does someone know an easy fix for this to put in a PR?

All HUD elements should also be clamped within the view.

Yes, this includes tooltips and dialogue as well.

I have no idea how to resize a Label properly for long text, but I do see some overflow action and maybe I'll have a branch for that too at some point.

I'm not sure how to deal with clamping the Label tooltip, please help and/or advise.
Problem is it's 989px wide, so doing the same magic math as on action menu to clamp it will move it totally to the wrong place.
Currently it works because the text is centered, and we get the center (along the x axis) of the node, and things kinda just work out.
Checking any width of the tooltip, though, will give insane results and that's why it's hard to check for overflows. I tested setting a very small size (width 12px) for the Label and regardless of how long the text in the tooltip is, tooltip.get_size() reports 90 pixels.
Is there a way to check the width of the contents of a Label?
I don't understand this at all, not sure how to proceed. Maybe the easiest thing would be to just to document this as "don't write tooltips that overflow and be mindful that it may overflow when translating to other languages", but is that ok?

Is there any reason why it needs to be so wide? As mentioned on IRC, I think a good compromise would be to narrow the width of the Label to a more reasonable size and clamp the Label, regardless of the actual width of the text inside it. This will cause some padding when the text doesn't fill the Label completely, but if the text can be centered, that will at least give a consistent look on the left and right edges of the viewport. This doesn't solve the issue of what to do with overflow, but I think we can fix and merge that in a later commit, once we've given it some more thought and have had some hands on experience with the immediate fix.

@fleskesvor I looked into this quite extensively after April 3rd and it has to be wide because there's no way to get the actual content width and clamp by that.
I think I have a fix for clamping to viewport as mentioned in 2a0be9428cb2b0d9df34d0978b8e979f33a69540
Stay tuned for more!

Looks like #224 took a third of this issue away, yay!

I just noticed that running our demo in fullscreen causes problems!
A long line of dialog near the edge of the screen causes it to overflow in fullscreen. Something is affecting the transform or something when in fullscreen mode :(


01_Responsive_layout

Mobile Responsive Layout using sm and lg.
Resposive Layout of add blog page

Hi @SankalpHaritash21, Thank you for this PR.
This PR needs to be rebased to the upstream branch as I have made recent changes.
If that seems challenging to you, Try to get a fresh clone of the project and create a branch and while raising PR make sure all the list is checked in the list. That would help to merge your requests faster :)
Let me know if you have any further doubt here, happy to help


Configuring things.: What's the best way to pass parameters?

So, we had many of our parameters as EnvVars, but to make things work from mvn jetty:run we changed them to System.getProperties.  Lots of mvn jetty:run support.
So, I thought, I can just pass -Djetty.SystemPropertiesFile=WEB-INF/my.properties in app.yaml JAVA_OPTS (also do the same for -Djava.util.logging.config.file=WEB-INF/logging.properties).
It turns out that at some point, things changed, and instead of coping the unexploded war, we appear to just copy root.war to webapp/ - so those files are in the war, and not easily accessible.
I was trying to make it so that we only define things once.  I can probably just define each property as -DmyProperty.thing1=foo -DmyProperty.thing2=bar, but that will imply a very long JAVA_OPTS, and then redefine them in the plugin.
Is there a better way?
And what about logging.properties, there really should be something easy for that?

Maybe you should explore the JDNI standard:
http://stackoverflow.com/questions/3804396/how-to-portably-read-configuration-data-from-a-servlet/3804788#3804788
We did not support it in GAE standard, but it something we should look at
more now.
On Wed, Mar 2, 2016 at 11:19 PM, Les Vogel<EMAIL_ADDRESS>wrote:

So, we had many of our parameters as EnvVars, but to make things work from mvn
jetty:run we changed them to System.getProperties. Lots of mvn jetty:run
support.
So, I thought, I can just pass
-Djetty.SystemPropertiesFile=WEB-INF/my.properties in app.yaml JAVA_OPTS
(also do the same for
-Djava.util.logging.config.file=WEB-INF/logging.properties).
It turns out that at some point, things changed, and instead of coping the
unexploded war, we appear to just copy root.war to webapp/ - so those files
are in the war, and not easily accessible.
I was trying to make it so that we only define things once. I can probably
just define each property as -DmyProperty.thing1=foo
-DmyProperty.thing2=bar, but that will imply a very long JAVA_OPTS, and
then redefine them in the plugin.
Is there a better way?
And what about logging.properties, there really should be something easy
for that?
—
Reply to this email directly or view it on GitHub
https://github.com/GoogleCloudPlatform/appengine-java-vm-runtime/issues/197
.

Hmmm I'm not a huge fan of JNDI... it is really rather over complex for a map of map of map... of strings.
In jetty generally we have gone away from both Env vars and System properties as a configuration mechanism.   The main reason for this is that they both apply to the entire JVM and thus can often be too course grained, as we can have multiple servers per JVM and multiple contexts per server etc.
Thus our preferred configuration style is to use parameterised XML to do dependency injection as we assemble the POJOs that make up jetty.     Typically we can now configure jetty without making any XML changes, but by simply adding pre-prepared XMLs to the command line (typically with a module) and providing parameters via: command line; start.d/ini files; or a foobar.properties file on the command line.
For the compat image we already have a gae.xml file that can be extended and have more properties defined, which can then be injected as attributes on the server.
We also have a root.xml file, that can inject into the context, although I will have to check if all the start properties are available to it.     It can inject attributes and/or init params into the context.

Note also for logging.properties, it should be possible to just put that in $JETTY_BASE/resources and have it found the normal way.  This is where we put the jetty-logging.properties file that directs the jetty log to java.util.log, so it would make more sense to have the java.util.log there as a resource of the JVM rather than burried as a resource of the webapp (or perhaps we can support either location?)

Note: the logging.properties for java.util.logging is not auto-discovered, auto-loaded by java itself.  You have to write code to find and load that file (unlike literally every other java logging framework on the planet)

For compat images logging.properties is configured in appengine-web.xml by setting the java.util.logging system property.

Configuring logging should be handled differently than configuring other aspects of the application.
Having logging initialized early (as early in the JVM as possible) is the best decision with logging.
Let's take the VmRuntimeFileLogHandler as an example, if we configure Jetty and its logging to initialize Jetty and java.util.logging (at the same time) we can have VmRuntimeFileLogHandler present even when other components are attempting to configure themselves at a later point.
This sort of early initialization is already baked into Jetty with jetty-logging.properties and the auto loading of java.util.logging configuration if you use the JavaUtilLog implementation in jetty.

Building on @joakime comments...
Currently the VmRuntimeFileLogHandler is configured in code, in the init method.   A moderate amount of code is run before then, so it is essentially running without a well configured log.  If something goes wrong before that, it will not be logged.
It would make more sense to configure this core logging in a logging.properties file that is installed in the image next to the jetty-logging.properties file, so all the base logging config would be in one spot and easy to see/understand/modify.
However, I do understand that parsing the appengine-web.xml allows additional system properties to be set that alter the behaviour of the log (log directory and logfile pattern), hence the current need to reload logging configuration.  I think it would be better to deprecate those environment variables and only use the logging.properties file for the core configuration (users can replace in a Dockerfile if they want).
Then any logging.properties file specified in appengine-web.xml could potentially be considered as additional logging configuration, that is additively applied (ie can't change the VmRuntimeLogFileHandler).

For those using the Generally Available Flex Env, note that JETTY_ARGS is the param for passing in logging.properties


Remove duplicate records with multi value fields

Codecov Report

Merging #252 (1d45cec) into 2021.3.x (dd8fdd6) will increase coverage by 0.00%.
The diff coverage is n/a.

@@            Coverage Diff            @@
##           2021.3.x     #252   +/-   ##
=========================================
  Coverage     41.96%   41.96%
=========================================
  Files           602      602
  Lines         60902    60902
=========================================
+ Hits          25559    25560    +1
+ Misses        32405    32404    -1
  Partials       2938     2938

Impacted Files
Coverage Δ

compose/types/record_value.go
72.30% <ø> (ø)

pkg/codegen/store.go
0.00% <ø> (ø)

pkg/handle/handle.go
85.71% <0.00%> (+7.14%)
:arrow_up:

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update dd8fdd6...1d45cec. Read the comment docs.

Merged


Appending SWIFT_STANDARD_LIBRARY_SWIFT_FLAGS to SWIFT_COMPILE_FLAGS.

Adding a compile file to add more paths to libs and roots and such.

@swift-ci please test

CC: @Rostepher @gottesmm


[1.16.5] Beta server crash w/ forge 36.0.22

crashes with latest beta release
https://gist.github.com/MrKashew/dc92252f7863e9a00a0515562701a308
https://gist.github.com/MrKashew/0877e4f5a2df0588824381115eaafeba

I don't see why these methods/classes don't exist for you. Despite the fact that they're not classes added by Forge it would be worth trying to update Forge.

I know this isn't the proper place to ask, but I couldn't find anywhere else to do it: How can I use the 1.16 version of this mod? Every download link I've found on the Internet so far is for the 1.12 version. I am unable to compile from source, so is there a way to do it without having to download the source code and compiling?

You should be able to compile from source via the instructions in the readme but we also put download links to the beta in the discord, though we also stress that is should be used for testing only because it's unfinished and unstable.

updated to forge 36.0.42, here is the new server crash, lmk if u have any insight
https://gist.github.com/MrKashew/0b97463937572951977d9a2010b613ef

Ah you're running on a dedicated server? Literally just noticed that class is a client-side only, will be a simple fix.

Forgot to say that this has been fixed on the custom tree data branch.

@Harleyoc1 is the custom tree data branch in a stable playable state (should I compile and test on my server or wait for the changes to be checked out to main 1.16 branch)

I wouldn't call it stable but neither is the main 1.16 branch. Playable? Probably. It still needs a lot of work and testing so someone using it might actually help, and since you're already prepared to test the 1.16 branch I'd say you'd be fine running this. Also note that you'd need to build the branch on DT plus too if you’re using that, as the old one won't work.

@Harleyoc1 havent been able to build on that branch, is this a me problem?


PDF export misses footers and headers

Describe the Bug

PDF export misses footers and headers
Steps to Reproduce

Go to the page tree
Select pages for PDF export
Export pages

Expected Behavior

Exported PDFs are expected to have a footer and header on each page
Actual Behavior

Footer is only present on first page at the top, header missing entirely
Additional Information

Warning about name of integreat logo in logs

Traceback
expected string or bytes-like object
Mar 29 07:58:26 WARNING xhtml2pdf - Need a valid file name!
'<img src="/static/images/integreat-logo.jpg" height="40"/>'
Mar 29 08:49:57 INFO django.server - "POST /nurnberg/pages/de/export/ HTTP/1.1" 200 12735

This is probably related to https://github.com/xhtml2pdf/xhtml2pdf/issues/602


Fix wheel tests for Rocky Linux 8.

Recently RAPIDS added wheel tests on Rocky Linux 8. This requires a change to support installing dependencies with yum on Rocky Linux 8.

See failure here: https://github.com/rapidsai/cucim/actions/runs/10714414951/job/29708066437#step:8:227
ci/test_wheel.sh: line 14: apt: command not found

/merge

@jameslamb I thought yesterday that I had missed something in this PR, but it turns out that we're fine. We need EPEL (Extra Packages for Enterprise Linux) to get openslide, but we're already enabling that in citestwheel: https://github.com/rapidsai/ci-imgs/blob/e0ffa9bae4a6cb998e594a1f9bc3916780516b21/citestwheel.Dockerfile#L47
So we're in the clear, and this PR was sufficient to make nightly tests pass.

ah great, thanks for that!


CVE-2023-36665 Gax < 4.3.6 has critical vulnerability in Protobufjs dependency

Gax > 4.3.6 has critical vulnerability in Protobufjs dependency
https://github.com/googleapis/google-cloud-node/blob/140597eac392a4b777c3e0fda197544b0f2912ba/packages/google-cloud-monitoring/package.json#L51
See issue in Gax repo here
It's a critical vulnerability that's already fixed downstream, you just need update a dependency.

Done! Thanks.


Remove support for animation progress event

Basically, the progress event in iOS is useless, it neither gets you the current values of the animated properties (they are at their destination value), nor can we provide an progress value (0-1 after easing). We should re-write the tray demo and then remove support for the progress event.

+1 to remove the progress event. Progress information is not available on iOS, so any intermediate values we'd provide in the event would be just guessing.


Bootstrap 4 cannot be used?

Is anyone having the same problem that bootstrap cannot function?

Bug reports must include a live demo of the issue. Per our contributing guidelines, please create a reduced test case via CodePen or JS Bin and report back with your link, Bootstrap version, and specific browser and operating system details.

This is a saved reply.


Auto-disable ANN rules when a type-checker is detected

Ruff is a great linter and formatter, we all don't have any doubts 👍🏼 Thank you guys for maintaining this project!
However, Ruff also includes some rules related to static type-checking, for example: ANN.
Could it be possible to set up Ruff to automatically turn off those rules if a type-checker is configured, let us say mypy? In such a scenario, extra "type-checking" with Ruff looks like a redundancy to me.

Ruff is a great linter and formatter, we all don't have any doubts 👍🏼 Thank you guys for maintaining this project!

Thanks for the kind words

Could it be possible to set up Ruff to automatically turn off those rules if a type-checker is configured, let us say mypy? In such a scenario, extra "type-checking" with Ruff looks like a redundancy to me.

I see the motivation for this but we very intentionally avoid reading configurations from other tools to avoid surprising behavior and incompatibilities (we don't know the other tool's version and ruff would break if the tool changes its configuration).

However, Ruff also includes some rules related to static type-checking, for example: ANN.

Some of those rules indeed overlap with what some type checkers do, but I consider their nature to be mostly linting (it enforces a specific style) rather than correct typing. I think it's best if users disable the rules manually if they know that their type checker of choice already covers them.

OK. Thanks for an elaborate answer. 🙂


[WIP] Make logging from CD consistent with other controllers

With the change each log from CD will contain controller=clusterDeployment
Signed-off-by: Lalatendu Mohanty<EMAIL_ADDRESS>

/lgtm

/lgtm


Can't type in browser console [WIN]

Details

Chome Version 62.0.3202.62 (Official Build) (64-bit)
OS version: Windows 10 X64 1709
xterm.js version:  2.9.2

Issue:
When I try to access via a browser served by my webserver (IIS), I get a blank console, but I can't type anything, no errors in the browser console  See Screenshot below.
If I launch via NPM, "set PORT=8300&&npm run dev" I can access the demo site via localhost:8300 just fine.
Any idea what I'm doing wrong here?

Here is my index.html:
<!doctype html>

    <title>xterm.js demo</title>
    <link rel="stylesheet" href="/xterm/build/xterm.css" />
    <link rel="stylesheet" href="/xterm/build/addons/fullscreen/fullscreen.css" />
    <link rel="stylesheet" href="/xterm/build/style.css" />

@seanvree by most chances this is an issue at the "transport layer" of your application. I would suggest you inspecting the WebSocket connection in the "Network" tab in the developer tools of Chrome.
Since this is not actually a library issue, I am closing this issue now.
I suggest you asking a new question at https://stackoverflow.com and add the xtermjs, iis and the websocket tags.
We keep an eye on the xtermjs tag ourselves, but most probably this has to do more with IIS.

@parisk  yeah, I have no doubt it's an IIS issue as well, Just wondering if anyone had any tips.
Do you by chance KNOW if this works with IIS?
I'll check out SO.  Thanks!

@seanvree I have no idea about IIS in general, but if it's implementing the WebSocket protocol (I bet it does) it should work just fine.

@parisk thanks, I'll dig into it.


cannot revert changes and reinstall "bloatware"

a few useful programs that shouldn't be considered bloatware like the calculator i use almost daily got missed in the custom list and now i cant get it back after trying a restore point and Revert-Changes command.

managed to reinstall the store and get my apps back.


Happy Category is Not Found

{"message":"Not Found"}
when I try to access the end point

The endpoints
/sfw/kill
/sfw/happy
/sfw/wink
/sfw/poke
/sfw/dance
/sfw/cringe
Are all returning a 404 Not Found with the above mentioned JSON response, I assume this has something to do with the recent revert to GCP (#57)

FIxed

Thank you! <3


Ensure marvin package version is PEP440 compliant

Description
Fixes #7927
We replace the first occurence of - with + to make the marvin version PEP440 compliant

Types of changes

[ ] Breaking change (fix or feature that would cause existing functionality to change)
[ ] New feature (non-breaking change which adds functionality)
[x] Bug fix (non-breaking change which fixes an issue)
[ ] Enhancement (improves an existing feature and functionality)
[ ] Cleanup (Code refactoring and cleanup, that may add test cases)
[ ] build/CI

Feature/Enhancement Scale or Bug Severity
Feature/Enhancement Scale

[ ] Major
[x] Minor

Bug Severity

[ ] BLOCKER
[ ] Critical
[ ] Major
[x] Minor
[ ] Trivial

Screenshots (if appropriate):
How Has This Been Tested?

How did you try to break this feature and the system with this change?

@blueorangutan package

@vishesh92 a [SL] Jenkins job has been kicked to build packages. It will be bundled with  KVM, XenServer and VMware SystemVM templates. I'll keep you posted as I make progress.

Packaging result [SF]: ✔ el7 ✔ el8 ✔ el9 ✔ debian ✔ suse15. SL-JID 9885

@blueorangutan test

@vishesh92 a [SL] Trillian-Jenkins test job (centos7 mgmt + kvm-centos7) has been kicked to run smoke tests

clgtm, not sure though if it makes sense to change the version globally this way instead of in the marvin setup.

We are changing only for marvin here. I am not sure if changing this globally will break some other stuff or not.

[SF] Trillian test result (tid-10418)
Environment: kvm-centos7 (x2), Advanced Networking with Mgmt server 7
Total time taken: 42973 seconds
Marvin logs: https://github.com/blueorangutan/acs-prs/releases/download/trillian/pr9230-t10418-kvm-centos7.zip
Smoke tests completed. 130 look OK, 1 have errors, 0 did not run
Only failed and skipped tests results shown below:

Test
Result
Time (s)
Test File

test_02_trigger_shutdown
Failure
341.74
test_safe_shutdown.py


Pressing enter in search select should select the first item

The simple solution is to set focusFirstSuggestion to true on <Autosuggest>. There was a strange side-effect of doing this that involved the text field filling with the selected item while keeping the suggestions open. This is different behavior than pressing down and enter (which we wanted to duplicate), so I worked around it by adding a justCleared flag. Details here (and in line comments):
React-autosuggest automatically calls onChange with the first suggestion after clearing when focusFirstSuggestion is true. The problem is, this first suggestion is still the one that was just selected. This sets the text of the field to match the value of the just selected one while leaving the dropdown open. Ideal behavior for us is that pressing enter (and thus selecting the first item due to focusFirstSuggestion being true) operates the same as if we pressed down then enter, which clears the text field and hides the suggestions. So if we just cleared, ignore setting the value here.


cmd/firmware_update topic is not published when using a firmware_update workflow

Describe the bug

tedge-agent does not publish the {topic_id}/cmd/firmware_update retained message when a firmware_update.toml workflow is defined.
This results in the Cumulocity IoT Device Management UI not showing the firmware tab, as the tedge-mapper-c8y did not know receive the operation.
Publishing a manual retain message fixes the issue:
tedge mqtt pub te/device/example///cmd/firmware_update '{}' -r

The logs show that the workflow is being read correctly (at least there are no visible errors),
It seems to be limited to the firmware_update operation, as other workflows
2024-09-28T16:30:34.054226172Z  INFO tedge_agent::agent: tedge-agent starting
2024-09-28T16:30:34.054431171Z  INFO sm-agent: tedge_agent::agent: Starting tedge-agent v1.3.0
2024-09-28T16:30:34.054849399Z  INFO Runtime: Started
2024-09-28T16:30:34.057049651Z  INFO sm-agent: tedge_agent::operation_workflows: Using operation workflow definition from "/root/dummy/operations/device_profile.toml" for 'device_profile' operation
2024-09-28T16:30:34.058983497Z  INFO sm-agent: tedge_agent::operation_workflows: Using operation workflow definition from "/root/dummy/operations/firmware_update.toml" for 'firmware_update' operation
2024-09-28T16:30:34.060869688Z  INFO sm-agent: tedge_agent::operation_workflows: Using operation workflow definition from "/root/dummy/operations/custom_op.toml" for 'custom_op' operation

And below shows the retained messages registered
$ tedge mqtt sub 'te/device/example//#'
[te/device/example///cmd/restart] {}
[te/device/example///cmd/log_upload] {"types":["software-management"]}
[te/device/example///cmd/config_snapshot] {"types":["tedge-configuration-plugin","tedge-log-plugin","tedge.toml"]}
[te/device/example///cmd/config_update] {"types":["tedge-configuration-plugin","tedge-log-plugin","tedge.toml"]}
[te/device/example///cmd/software_list] {"types":[]}
[te/device/example///cmd/software_update] {"types":[]}
[te/device/example///cmd/custom_op] {}

To Reproduce

Add a valid firmware_update workflow
TEDGE_CONFIG_DIR=$PWD/dummy tedge init
cp /etc/tedge/operations/firmware_update.toml $PWD/dummy/operations

Start a tedge-agent

TEDGE_CONFIG_DIR=$PWD/dummy TEDGE_RUN_LOCK_FILES=false tedge-agent --mqtt-topic-root dummy --mqtt-device-topic-id device/example//

Stop the agent using ctrl-c (after about ~10 seconds)

Check the retained messages on the MQTT broker
TEDGE_CONFIG_DIR=$PWD/dummy tedge mqtt sub 'dummy/device/example//#'

Expected behavior

Screenshots

Environment (please complete the following information):

OS [incl. version]
Hardware [incl. revision]
System-Architecture [e.g. result of "uname -a"]
thin-edge.io version [e.g. 0.1.0]

Additional context

Fixed by #3146
Test Plan
The agent was updated to publish a firmware_update capability message when a workflow is defined for the same. The mapper was also updated to make sure that the c8y_Firmware operation mapping is enabled by default.

The bug cannot be reproduced anymore


Horizontal scaling gets squished when you resize Enso application jsut after the start with debug.dev-tools flag

Discord username
No response
What type of issue is this?
Permanent – Occurring repeatably
Is this issue blocking you from using Enso?

[ ] Yes, I can't use Enso because of this issue.

Is this a regression?

[ ] Yes, previous version of Enso did not have this issue.

What issue are you facing?
Enso horizontal resolution gets squished when you resize Enso screen horizontally (ie.: to have more space to work with). Also there's a navigation gap between the squished to the left side area and dev console (mouse teleports through the "gap" when you move it from left to right and vice versa).
https://user-images.githubusercontent.com/108271897/235632950-94f7ff15-c3d1-46eb-aed2-74ebdeba5847.mp4
Expected behaviour
No squashing of the resolution of GUI, expanding the application accordingly to the  window size.
How we can reproduce it?
launch Enso with debug.dev-tools flag and stretch the window it as soon as it appears.
first: observe the shashed cursor
then, after a project opens: entire GUI gets squished horizontally
Screenshots or screencasts
longer movie:
https://user-images.githubusercontent.com/108271897/235633631-667e0524-5ac1-43f5-a841-ff0b667230c6.mp4
Enso Version
2023.5.2 Nightly
Browser or standalone distribution
Standalone distribution
Browser Version or standalone distribution
standalone
Operating System
Windows
Operating System Version
Win11pro 22H2 22621.1555
Hardware you are using
12th Gen Intel(R) Core(TM) i9-12900HK / RTX3060 Laptop / Nvidia Drivers 531.68

Also what I have noticed on the next Enso launch is that the mouse position in the home screen was incorrectly displayed. I have clicked "new project" but instead I got several projects from the templates clicked instead of several new "unnamed" projects created.

This is a regression. I'm sure resizing worked before.

#6461 (commit cd92d90f9ff70320096bb514194c50796dcfcba0) is at fault.
An easier way to reproduce on the smaller screen (that doesn't allow extending the window):

Open the IDE with -debug.dev-tools
Make the window really narrow
Reload it (I use Electron menu View -> Reload)
Quickly enlarge the window

#6461 (commit cd92d90f9ff70320096bb514194c50796dcfcba0) is at fault.
An easier way to reproduce on the smaller screen (that doesn't allow extending the window):

Open the IDE with -debug.dev-tools
Make the window really narrow
Reload it (I use Electron menu View -> Reload)
Quickly enlarge the window

Are you 100% sure this commit caused it? It was not touching any code but code related to a widget, so I don't see why it could theoretically change anything like that.

This is a regression. I'm sure resizing worked before.

Are you sure this is regression? It's not about scaling. It's about calling fast before our graph editor loads.

Are you 100% sure this commit caused it? It was not touching any code but code related to a widget, so I don't see why it could theoretically change anything like that.

I checked one more time. I can reproduce it on this commit, and I can't do that on the previous one.

This is almost impossible as that code was not changing anything ... Anyway, I'd take a look at it.

I think it has to have been the previous one (a839545)--I changed the resize logic in #6458. I haven't been able to reproduce this, can someone check whether, once the bug is observed, further changing the screen size corrects it?

@vitvakatu as you are reproducing it (I can't, it somehow works on my end), could you check the above question from Kaz, please?

I have reproduced it. It's very timing-sensitive. Fixing it now.

@vitvakatu When you get a chance, could you verify that this is fixed for you also?


Allinone for switch

Hi,
Is it possible to add an option to have the attributes from the sensor in the switch? This would help a lot to create a dynamic list in a card with memory, cpu, status and the ability to turn on/off the containers.
Thanks.

It is not that issue to do this, because it is 2 different types (and next to it is possible, it will duplicate the code base). So for now, it is not possible and I will close the ticket.


Remove duplicate

[x] Bugfix (non-breaking change which fixes an issue)
[ ] New feature (non-breaking change which adds functionality)

Proposed changes

Just removes a duplicate object key.
Checklist

[x] I have read the contribution guidelines
[ ] For non-bugfix PRs, I have discussed this change on the forum/slack team.
[x] I have run grunt to verify the unit tests pass
[ ] I have added suitable unit tests to cover the new/changed functionality

Thanks!


Update create-users-and-assign-them-to-roles-via-the-scim-2-0-api-1ca…

…8c4a.md
The current help version has the wrong URL. The   (e.g. tenant.eu10.hcs.cloud.sap) has a different domain than the  (e.g. tenant.authentication.eu10.hana.ondemand.com) which is actually required for SCIM API.  Without this correction it is not possible to use the SCIM API.

Dear Mr. Males,
thank you for your feedback. We've checked your proposal and will apply the required changes.
Best Regards,
Cornelia Stier (from the SAP Datasphere User Assistance team)


How could you access the second item, ‘Lion’, in the following code block?

How could you access the second item, ‘Lion’, in the following code block?
const animalArray = ['Sloth', 'Lion', 'Chipmunk'];

A. animalArray['Lion'];
B. animalArray[2]
C. animalArray[1]

Answer: C. animalArray[1]


Data users new design

Got the Data Users section in the new and edit form to mimic the new design.
Closes #984
Empty table

Table with users

Modal to add new users

There are a few styling issues still pending that I documented on issue #1102

coverage: 84.301% (+0.01%) from 84.289%
when pulling 673961d028e1e4d61d1e76874b9a77f9be79f887 on 984-data-users
into 11683cc03b8f1295ea1352c22f690511297089dd on main.


Make directory deletion more atomic

Currently, if a file is locked in the destination directory (which will be the case if the app is running), it will end up partially deleted and the installation will exit with an error. This leaves the existing install in a corrupted state, which is less than ideal. This PR makes deletes sort-of atomic by first renaming the directory before deleting it. This rename will fail if the app is currently running or if the contents of the directory happen to be locked for some other reason

applying this to every DeleteDirectory seems like wielding a very large hammer. i don't have full context here but i feel like we would do ourselves a favor by specifically targeting whichever problematic case we're attempting to address.

Fair enough. I've changed the default value of moveFirst and only explicitly set it to true when attempting to delete the top-level destination directory in the full install action. The original thinking was that "atomic" deletes are a much more sensible default behavior, but looking at the other use cases partial deletes seem desirable in at least some of the usages (removing temporary directories created for unpacking artifacts for install).

it looks like attempts were made to handle exceptions inside this function and manifest them as error logs. we're essentially adding a new method of failure (a Directory.Move exception) that i'm not convinced would be well-received by all the callers.
wdyt about wrapping the first Directory.Move in a try/catch and just logging & returning in the catch?

In practice, this already throws in the most common error case (or at least what I assume to be our most common error case) because File.DeleteFile is not protected by a try/catch. It's not entirely clear to me why not throwing is desirable when the directory deletion is failed. Unfortunately that seems to be a decision that predates the current upstream repo.
Regardless, the throw on the initial move failure is needed for the "target" case. We need it to throw so it aborts the installation with an error rather than potentially corrupting the current install with a half applied new one. With the change above, it will only potentially throw in this case when we explicitly want it (at least now that I removed the throw ex; I added to the last catch block)

similarly, when attempting to restore state after a failed delete... what should happen if the move-back fails?

There's not really a good way to make sure the folder ultimately gets cleaned up in this case. I guess we could store the temp folder name somewhere and try to clean it up later, but that feels pretty gross.
Fortunately, I think this situation should be rare. I guess thinking about it more, it doesn't make sense to throw in this case as the target directory has been moved out of the way even if it's not completely gone


Issue/3039 modules v2 document dev to prod workflow

Description
This is still a work in progress, though I think the development part is mostly done (some minor changes might be needed related to the poetry install editable discussion). I though it might be good to open this part up for review already because as we've noticed this might spark new discussions. As a result it's probably a good idea to keep the feedback loop relatively small.
closes #3039
Self Check:
Strike through any lines that are not applicable (~~line~~) then check the box

[x] Attached issue to pull request
[x] Changelog entry
[ ] Type annotations are present
[ ] Code is clear and sufficiently documented
[ ] No (preventable) type errors (check using make mypy or make mypy-diff)
[ ] Sufficient test cases (reproduces the bug/tests the requested feature)
[x] Correct, in line with design
[x] End user documentation is included or an issue is created for end-user documentation (add ref to issue here: )

Reviewer Checklist:

[ ] Sufficient test cases (reproduces the bug/tests the requested feature)
[ ] Code is clear and sufficiently documented
[ ] Correct, in line with design

@wouterdb I think I've addressed all your comments. I've also added a section for distribution.

@wouterdb I think I've addressed all your comments. I've also added a section for distribution.

Processing this pull request

Failed to merge changes into iso4 due to merge conflict.Please open a pull request for these branches separately.

Merged into branches master in 2ba95e64fb2c791bcb654cd6066b6c512d04705c

Not closing this pull request due to previously commented issues for some of the destination branches. Please open a separate pull request for those branches. You can safely close this pull request and delete the source branch.