Repository: ClemensElflein/OpenMower
File: Firmware/LowLevel/src/main.cpp (C++)

Code before:
        ui_interval = msg->interval;
    }
}

void sendConfigMessage(const uint8_t pkt_type) {
    size_t msg_size = sizeof(struct ll_high_level_config) + 3;  // + 1 type + 2 crc
    uint8_t *msg = (uint8_t *)malloc(msg_size);
    if (msg == NULL)
        return;
    *msg = pkt_type;
    memcpy(msg + 1, &llhl_config, sizeof(struct ll_high_level_config));  // Copy our live config into the message, behind type
    sendMessage(msg, msg_size);
    free(msg);
}

/**
 * @brief applyConfig applies those members who are not undefined/unknown.
 * This function get called either when receiving a ll_high_level_config packet from HL, or during boot after read from LittleFS
 * @param buffer
 * @param size
 */
void applyConfig(const uint8_t *buffer, const size_t size) {
    // This is a flexible length packet where the size may vary when ll_high_level_config struct get enhanced only on one side.
    // If payload size is larger than our struct size, ensure that we only copy those we know of = our struct size.
    // If payload size is smaller than our struct size, copy only the payload we got, but ensure that the unsent member(s) have reasonable defaults.
    size_t payload_size = min(sizeof(ll_high_level_config), size - 2);  // exclude crc

    // Use a temporary rcv_config for easier member access.
    // If payload is smaller (older), struct already contains reasonable defaults.
    ll_high_level_config rcv_config;
    memcpy(&rcv_config, buffer, payload_size);  // Copy payload to temporary config

    // Use a temporary new_config for save sanity adaption before copy to our live config.
    // It's important to use a clean one with LL-default values, and not the current live llhl_config one,
    // because we need to ensure that re-commented mower_config parameter get the LL-default value and not the last saved one!
    ll_high_level_config new_config;

    // Take over of payload values. This could also be made the other way around, which would save the copy of the "leading"
    // values, but I felt it's more secure to do it this way (in special for further packet enlargements)

    // HL always force these member
    new_config.options = rcv_config.options;
    strncpy(new_config.language, rcv_config.language, 2);

    // Take over only those (HL) values which are not "undefined/unknown"
    if (rcv_config.rain_threshold != 0xffff) new_config.rain_threshold = rcv_config.rain_threshold;
    if (rcv_config.v_charge_cutoff >= 0) new_config.v_charge_cutoff = min(rcv_config.v_charge_cutoff, 36.0f);  // Rated max. limited by MAX20405
    if (rcv_config.i_charge_cutoff >= 0) new_config.i_charge_cutoff = min(rcv_config.i_charge_cutoff, 5.0f);   // Absolute max. limited by D2/D3 Schottky
    if (rcv_config.v_battery_cutoff >= 0) new_config.v_battery_cutoff = rcv_config.v_battery_cutoff;
    if (rcv_config.v_battery_empty >= 0) new_config.v_battery_empty = rcv_config.v_battery_empty;
    if (rcv_config.v_battery_full >= 0) new_config.v_battery_full = rcv_config.v_battery_full;

Review comment (suggestion):
I think we should subtract 2 where the method is called, so size is really the amount of bytes in the buffer that are relevant for the method.

Code after:

void sendConfigMessage(const uint8_t pkt_type) {
    size_t msg_size = sizeof(struct ll_high_level_config) + 3;  // + 1 type + 2 crc
    uint8_t *msg = (uint8_t *)malloc(msg_size);
    if (msg == NULL)
        return;
    msg[0] = pkt_type;
    memcpy(msg + 1, &llhl_config, sizeof(struct ll_high_level_config));  // Copy our live config into the message, behind type
    sendMessage(msg, msg_size);                                          // sendMessage() also calculate the packet CRC
    free(msg);
}

/**
 * @brief applyConfig applies those members who are not undefined/unknown.
 * This function get called either when receiving a ll_high_level_config packet from HL, or during boot after read from LittleFS
 * @param buffer
 * @param size of buffer (without packet type nor CRC)
 */
void applyConfig(const uint8_t *buffer, const size_t size) {
    // This is a flexible length packet where the size may vary when ll_high_level_config struct get enhanced only on one side.
    // If payload size is larger than our struct size, ensure that we only copy those we know of (which is our struct size).
    // If payload size is smaller than our struct size, copy only the payload we got, but ensure that the unsent member(s) have reasonable defaults.
    size_t payload_size = min(sizeof(ll_high_level_config), size);

    // Use a temporary rcv_config for easier member access.
    // If payload is smaller (older), struct already contains reasonable defaults.
    ll_high_level_config rcv_config;
    memcpy(&rcv_config, buffer, payload_size);  // Copy payload to temporary config

    // Use a temporary new_config for save sanity adaption before copy to our live config.
    // It's important to use a clean one with LL-default values, and not the current live llhl_config one,
    // because we need to ensure that re-commented mower_config parameter get the LL-default value and not the last saved one!
    ll_high_level_config new_config;

    // HL always force language
    strncpy(new_config.language, rcv_config.language, 2);

    // Take over only those (HL) values which are not "undefined/unknown"
    if (rcv_config.options.dfp_is_5v != OptionState::UNDEFINED) new_config.options.dfp_is_5v = rcv_config.options.dfp_is_5v;
    if (rcv_config.options.background_sounds != OptionState::UNDEFINED) new_config.options.background_sounds = rcv_config.options.background_sounds;
    if (rcv_config.options.ignore_charging_current != OptionState::UNDEFINED) new_config.options.ignore_charging_current = rcv_config.options.ignore_charging_current;
    if (rcv_config.rain_threshold != 0xffff) new_config.rain_threshold = rcv_config.rain_threshold;
    if (rcv_config.v_charge_cutoff >= 0) new_config.v_charge_cutoff = min(rcv_config.v_charge_cutoff, 36.0f);  // Rated max. limited by MAX20405
    if (rcv_config.i_charge_cutoff >= 0) new_config.i_charge_cutoff = min(rcv_config.i_charge_cutoff, 5.0f);   // Absolute max. limited by D2/D3 Schottky
    if (rcv_config.v_battery_cutoff >= 0) new_config.v_battery_cutoff = rcv_config.v_battery_cutoff;
    if (rcv_config.v_battery_empty >= 0) new_config.v_battery_empty = rcv_config.v_battery_empty;
    if (rcv_config.v_battery_full >= 0) new_config.v_battery_full = rcv_config.v_battery_full;
    if (rcv_config.lift_period != 0xffff) new_config.lift_period = rcv_config.lift_period;
    if (rcv_config.tilt_period != 0xffff) new_config.tilt_period = rcv_config.tilt_period;
    if (rcv_config.shutdown_esc_max_pitch != 0xff) new_config.shutdown_esc_max_pitch = rcv_config.shutdown_esc_max_pitch;
    if (rcv_config.volume != 0xff) new_config.volume = rcv_config.volume;