static void lap_started_event(const tiny_millis_t time, const GeoPoint *sp,
                              const float distance)
{
    pr_debug_int_msg("Starting lap ", ++g_lap);
    g_at_sf = true;

    // Timing and predictive timing
    start_lap_timing(time);
    startLap(sp, time);
    reset_elapsed_time();

    // Reset the sector logic
    g_lastSectorTimestamp = time;
    g_sector = 0;
    update_sector_geo_circle(g_sector);

    // Reset distance logic
    set_distance(distance);

    /*
     * Reset our finishGeoTrigger so that we get away from
     * finish before we re-arm the system.
     */
    resetGeoTrigger(&g_finish_geo_trigger);
}
/**
 * Calculates the highest quiet period usable based on the timer
 * configurations.  We calculate this by figuring out the expected
 * maximum HZ of the engine based in the info provided.  Then we simply
 * take the inverse of that and multiply it by the number of micro seconds
 * in a second.
 */
static uint32_t calc_quiet_period(const TimerConfig *tc,
                                  const float period)
{
        const float max_rpm = tc->cfg.max;
        const float ppr = (float) tc->pulsePerRevolution;
        const float max_hz = max_rpm * ppr * MAX_RPM_MULT / period;

        const uint32_t res = max_hz ? US_IN_A_SEC / max_hz : 0;
        pr_debug_int_msg("[timer] Calculated QP: ", res);
        return res;
}
/**
 * Called whenever we have hit a sector boundary.
 */
static void sector_boundary_event(const GpsSnapshot *gpsSnapshot)
{
    const tiny_millis_t millis = gpsSnapshot->deltaFirstFix;

    pr_debug_int_msg("Sector boundary ", g_sector);

    g_lastSectorTime = millis - g_lastSectorTimestamp;
    g_lastSectorTimestamp = millis;
    g_lastSector = g_sector;
    g_at_sector = true;
    update_sector_geo_circle(++g_sector);
}
static int getSignalStrength(Serial *serial)
{
    int res = sendCommand(serial, "AT+CSQ\r", "+CSQ:");
    if (res != NO_CELL_RESPONSE && strlen(g_cellBuffer) > 6) {
        char *next_start = NULL;
        char *rssi_string = strtok_r(g_cellBuffer + 6, ",", &next_start);
        if (rssi_string != NULL) {
            g_cell_signal_strength = modp_atoi(rssi_string);
            pr_debug_int_msg("Cell: signal strength: ", g_cell_signal_strength);
        }
    }
    return res;
}
TESTABLE_STATIC int flush_logfile(struct logging_status *ls)
{
        if (ls->writing_status != WRITING_ACTIVE)
                return -1;

        if (!isTimeoutMs(ls->flush_tick, FLUSH_INTERVAL_MS))
                return -2;

        pr_debug("Logging: flush\r\n");
        const int res = f_sync(g_logfile);
        if (0 != res)
                pr_debug_int_msg("Logging: flush err ", res);

        ls->flush_tick = xTaskGetTickCount();
        return res;
}
static int init_sample_ring_buffer(LoggerConfig *loggerConfig)
{
        const size_t channel_count = get_enabled_channel_count(loggerConfig);
        struct sample *s = g_sample_buffer;
        const struct sample * const end = s + LOGGER_MESSAGE_BUFFER_SIZE;
        int i;

        for (i = 0; s < end; ++s, ++i) {
                const size_t bytes = init_sample_buffer(s, channel_count);
                if (0 == bytes) {
                        /* If here, then can't alloc memory for buffers */
                        pr_error("Failed to allocate memory for sample buffers\r\n");
                        break;
                }
        }

        pr_debug_int_msg("Sample buffers allocated: ", i);
        return i;
}
/**
 * Called whenever we finish a lap.
 */
static void lap_finished_event(const GpsSnapshot *gpsSnapshot)
{
    pr_debug_int_msg("Finished lap ", ++g_lapCount);

    end_lap_timing(gpsSnapshot);
    finishLap(gpsSnapshot);
    g_at_sf = true;
    lc_reset();

    /*
     * In Stage mode, enforce a de-bounce trigger for start to ensure
     * we don't accidentally trigger a false start if the finish line
     * and start line are close together.
     *
     * We only do this in STAGE mode becuse in circuit mode the start
     * line _is_ the finish line and thus resetting this trigger in
     * CIRCUIT mode would cause our start logic to function improperly.
     */
    if (g_active_track->track_type == TRACK_TYPE_STAGE)
        resetGeoTrigger(&g_start_geo_trigger);
}
static void fileWriterTask(void *params)
{
        LoggerMessage *msg = NULL;
        struct logging_status ls;
        memset(&ls, 0, sizeof(struct logging_status));

        while(1) {
                int rc = -1;

                /* Get a sample. */
                xQueueReceive(g_sampleRecordQueue, &(msg), portMAX_DELAY);

                switch (msg->type) {
                case LoggerMessageType_Sample:
                        rc = logging_sample(&ls, msg);
                        break;
                case LoggerMessageType_Start:
                        rc = logging_start(&ls);
                        break;
                case LoggerMessageType_Stop:
                        rc = logging_stop(&ls);
                        break;
                default:
                        pr_warning("Unsupported message type\r\n");
                }

                /* Turns the LED on if things are bad, off otherwise. */
                error_led(rc);
                if (rc) {
                        pr_debug("Msg type ");
                        pr_debug_int(msg->type);
                        pr_debug_int_msg(" failed with code ", rc);
                }

                flush_logfile(&ls);
        }
}