/**
 ******************************************************************************
 * @addtogroup OpenPilotModules OpenPilot Modules
 * @{
 * @addtogroup ActuatorModule Actuator Module
 * @brief Compute servo/motor settings based on @ref ActuatorDesired "desired actuator positions" and aircraft type.
 * This is where all the mixing of channels is computed.
 * @{
 *
 * @file       actuator.c
 * @author     The LibrePilot Project, http://www.librepilot.org Copyright (C) 2015.
 *             The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
 * @brief      Actuator module. Drives the actuators (servos, motors etc).
 *
 * @see        The GNU Public License (GPL) Version 3
 *
 *****************************************************************************/
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */


#include <openpilot.h>

#include "accessorydesired.h"
#include "actuator.h"
#include "actuatorsettings.h"
#include "systemsettings.h"
#include "actuatordesired.h"
#include "actuatorcommand.h"
#include "flightstatus.h"
#include <flightmodesettings.h>
#include "mixersettings.h"
#include "mixerstatus.h"
#include "cameradesired.h"
#include "hwsettings.h"
#include "manualcontrolcommand.h"
#include "taskinfo.h"
#include <systemsettings.h>
#include <sanitycheck.h>
#ifndef PIOS_EXCLUDE_ADVANCED_FEATURES
#include <vtolpathfollowersettings.h>
#endif
#undef PIOS_INCLUDE_INSTRUMENTATION
#ifdef PIOS_INCLUDE_INSTRUMENTATION
#include <pios_instrumentation.h>
static int8_t counter;
// Counter 0xAC700001 total Actuator body execution time(excluding queue waits etc).
#endif

// Private constants
#define MAX_QUEUE_SIZE                   2

#if defined(PIOS_ACTUATOR_STACK_SIZE)
#define STACK_SIZE_BYTES                 PIOS_ACTUATOR_STACK_SIZE
#else
#define STACK_SIZE_BYTES                 1312
#endif

#define TASK_PRIORITY                    (tskIDLE_PRIORITY + 4) // device driver
#define FAILSAFE_TIMEOUT_MS              100
#define MAX_MIX_ACTUATORS                ACTUATORCOMMAND_CHANNEL_NUMELEM

#define CAMERA_BOOT_DELAY_MS             7000

#define ACTUATOR_ONESHOT_CLOCK           12000000
#define ACTUATOR_ONESHOT125_PULSE_FACTOR 1.5f
#define ACTUATOR_ONESHOT42_PULSE_FACTOR  0.5f
#define ACTUATOR_MULTISHOT_PULSE_FACTOR  0.24f
#define ACTUATOR_PWM_CLOCK               1000000
// Private types


// Private variables
static xQueueHandle queue;
static xTaskHandle taskHandle;
static FrameType_t frameType = FRAME_TYPE_MULTIROTOR;
static SystemSettingsThrustControlOptions thrustType = SYSTEMSETTINGS_THRUSTCONTROL_THROTTLE;
static bool camStabEnabled;
static bool camControlEnabled;

static uint8_t pinsMode[MAX_MIX_ACTUATORS];
// used to inform the actuator thread that actuator update rate is changed
static ActuatorSettingsData actuatorSettings;
static bool spinWhileArmed;

// used to inform the actuator thread that mixer settings are changed
static MixerSettingsData mixerSettings;
static int mixer_settings_count = 2;

// Private functions
static void actuatorTask(void *parameters);
static int16_t scaleChannel(float value, int16_t max, int16_t min, int16_t neutral);
static int16_t scaleMotor(float value, int16_t max, int16_t min, int16_t neutral, float maxMotor, float minMotor, bool armed, bool alwaysStabilizeWhenArmed, float throttleDesired);
static void setFailsafe();
static float MixerCurveFullRangeProportional(const float input, const float *curve, uint8_t elements, bool multirotor);
static float MixerCurveFullRangeAbsolute(const float input, const float *curve, uint8_t elements, bool multirotor);
static bool set_channel(uint8_t mixer_channel, uint16_t value);
static void actuator_update_rate_if_changed(bool force_update);
static void MixerSettingsUpdatedCb(UAVObjEvent *ev);
static void ActuatorSettingsUpdatedCb(UAVObjEvent *ev);
static void SettingsUpdatedCb(UAVObjEvent *ev);
float ProcessMixer(const int index, const float curve1, const float curve2,
                   ActuatorDesiredData *desired,
                   bool multirotor, bool fixedwing);

// this structure is equivalent to the UAVObjects for one mixer.
typedef struct {
    uint8_t type;
    int8_t  matrix[5];
} __attribute__((packed)) Mixer_t;

/**
 * @brief Module initialization
 * @return 0
 */
int32_t ActuatorStart()
{
    // Start main task
    xTaskCreate(actuatorTask, "Actuator", STACK_SIZE_BYTES / 4, NULL, TASK_PRIORITY, &taskHandle);
    PIOS_TASK_MONITOR_RegisterTask(TASKINFO_RUNNING_ACTUATOR, taskHandle);
#ifdef PIOS_INCLUDE_WDG
    PIOS_WDG_RegisterFlag(PIOS_WDG_ACTUATOR);
#endif
    SettingsUpdatedCb(NULL);
    MixerSettingsUpdatedCb(NULL);
    ActuatorSettingsUpdatedCb(NULL);
    return 0;
}

/**
 * @brief Module initialization
 * @return 0
 */
int32_t ActuatorInitialize()
{
    // Register for notification of changes to ActuatorSettings
    ActuatorSettingsConnectCallback(ActuatorSettingsUpdatedCb);

    // Register for notification of changes to MixerSettings
    MixerSettingsConnectCallback(MixerSettingsUpdatedCb);

    // Listen for ActuatorDesired updates (Primary input to this module)
    ActuatorDesiredInitialize();
    queue = xQueueCreate(MAX_QUEUE_SIZE, sizeof(UAVObjEvent));
    ActuatorDesiredConnectQueue(queue);

    // Register AccessoryDesired (Secondary input to this module)
    AccessoryDesiredInitialize();

    // Check if CameraStab module is enabled
    HwSettingsOptionalModulesData optionalModules;
    HwSettingsOptionalModulesGet(&optionalModules);
    camStabEnabled    = (optionalModules.CameraStab == HWSETTINGS_OPTIONALMODULES_ENABLED);
    camControlEnabled = (optionalModules.CameraControl == HWSETTINGS_OPTIONALMODULES_ENABLED);
    // Primary output of this module
    ActuatorCommandInitialize();

#ifdef DIAG_MIXERSTATUS
    // UAVO only used for inspecting the internal status of the mixer during debug
    MixerStatusInitialize();
#endif

#ifndef PIOS_EXCLUDE_ADVANCED_FEATURES
    VtolPathFollowerSettingsConnectCallback(&SettingsUpdatedCb);
#endif
    SystemSettingsConnectCallback(&SettingsUpdatedCb);

    return 0;
}
MODULE_INITCALL(ActuatorInitialize, ActuatorStart);

/**
 * @brief Main Actuator module task
 *
 * Universal matrix based mixer for VTOL, helis and fixed wing.
 * Converts desired roll,pitch,yaw and throttle to servo/ESC outputs.
 *
 * Because of how the Throttle ranges from 0 to 1, the motors should too!
 *
 * Note this code depends on the UAVObjects for the mixers being all being the same
 * and in sequence. If you change the object definition, make sure you check the code!
 *
 * @return -1 if error, 0 if success
 */
static void actuatorTask(__attribute__((unused)) void *parameters)
{
    UAVObjEvent ev;
    portTickType lastSysTime;
    portTickType thisSysTime;
    uint32_t dTMilliseconds;

    ActuatorCommandData command;
    ActuatorDesiredData desired;
    MixerStatusData mixerStatus;
    FlightModeSettingsData settings;
    FlightStatusData flightStatus;
    float throttleDesired;
    float collectiveDesired;

#ifdef PIOS_INCLUDE_INSTRUMENTATION
    counter = PIOS_Instrumentation_CreateCounter(0xAC700001);
#endif
    /* Read initial values of ActuatorSettings */

    ActuatorSettingsGet(&actuatorSettings);

    /* Read initial values of MixerSettings */
    MixerSettingsGet(&mixerSettings);

    /* Force an initial configuration of the actuator update rates */
    actuator_update_rate_if_changed(true);

    // Go to the neutral (failsafe) values until an ActuatorDesired update is received
    setFailsafe();

    // Main task loop
    lastSysTime = xTaskGetTickCount();
    while (1) {
#ifdef PIOS_INCLUDE_WDG
        PIOS_WDG_UpdateFlag(PIOS_WDG_ACTUATOR);
#endif

        // Wait until the ActuatorDesired object is updated
        uint8_t rc = xQueueReceive(queue, &ev, FAILSAFE_TIMEOUT_MS / portTICK_RATE_MS);
#ifdef PIOS_INCLUDE_INSTRUMENTATION
        PIOS_Instrumentation_TimeStart(counter);
#endif

        if (rc != pdTRUE) {
            /* Update of ActuatorDesired timed out.  Go to failsafe */
            setFailsafe();
            continue;
        }

        // Check how long since last update
        thisSysTime    = xTaskGetTickCount();
        dTMilliseconds = (thisSysTime == lastSysTime) ? 1 : (thisSysTime - lastSysTime) * portTICK_RATE_MS;
        lastSysTime    = thisSysTime;

        FlightStatusGet(&flightStatus);
        FlightModeSettingsGet(&settings);
        ActuatorDesiredGet(&desired);
        ActuatorCommandGet(&command);

        // read in throttle and collective -demultiplex thrust
        switch (thrustType) {
        case SYSTEMSETTINGS_THRUSTCONTROL_THROTTLE:
            throttleDesired = desired.Thrust;
            ManualControlCommandCollectiveGet(&collectiveDesired);
            break;
        case SYSTEMSETTINGS_THRUSTCONTROL_COLLECTIVE:
            ManualControlCommandThrottleGet(&throttleDesired);
            collectiveDesired = desired.Thrust;
            break;
        default:
            ManualControlCommandThrottleGet(&throttleDesired);
            ManualControlCommandCollectiveGet(&collectiveDesired);
        }

        bool armed = flightStatus.Armed == FLIGHTSTATUS_ARMED_ARMED;
        bool activeThrottle   = (throttleDesired < -0.001f || throttleDesired > 0.001f); // for ground and reversible motors
        bool positiveThrottle = (throttleDesired > 0.00f);
        bool multirotor  = (GetCurrentFrameType() == FRAME_TYPE_MULTIROTOR); // check if frame is a multirotor.
        bool fixedwing   = (GetCurrentFrameType() == FRAME_TYPE_FIXED_WING); // check if frame is a fixedwing.
        bool alwaysArmed = settings.Arming == FLIGHTMODESETTINGS_ARMING_ALWAYSARMED;
        bool alwaysStabilizeWhenArmed = flightStatus.AlwaysStabilizeWhenArmed == FLIGHTSTATUS_ALWAYSSTABILIZEWHENARMED_TRUE;

        if (alwaysArmed) {
            alwaysStabilizeWhenArmed = false; // Do not allow always stabilize when alwaysArmed is active. This is dangerous.
        }
        // safety settings
        if (!armed) {
            throttleDesired = 0.00f; // this also happens in scaleMotors as a per axis check
        }

        if ((frameType == FRAME_TYPE_GROUND && !activeThrottle) || (frameType != FRAME_TYPE_GROUND && throttleDesired <= 0.00f) || !armed) {
            // throttleDesired should never be 0 or go below 0.
            // force set all other controls to zero if throttle is cut (previously set in Stabilization)
            // todo: can probably remove this
            if (!(multirotor && alwaysStabilizeWhenArmed && armed)) { // we don't do this if this is a multirotor AND AlwaysStabilizeWhenArmed is true and the model is armed
                if (actuatorSettings.LowThrottleZeroAxis.Roll == ACTUATORSETTINGS_LOWTHROTTLEZEROAXIS_TRUE) {
                    desired.Roll = 0.00f;
                }
                if (actuatorSettings.LowThrottleZeroAxis.Pitch == ACTUATORSETTINGS_LOWTHROTTLEZEROAXIS_TRUE) {
                    desired.Pitch = 0.00f;
                }
                if (actuatorSettings.LowThrottleZeroAxis.Yaw == ACTUATORSETTINGS_LOWTHROTTLEZEROAXIS_TRUE) {
                    desired.Yaw = 0.00f;
                }
            }
        }

#ifdef DIAG_MIXERSTATUS
        MixerStatusGet(&mixerStatus);
#endif

        if ((mixer_settings_count < 2) && !ActuatorCommandReadOnly()) { // Nothing can fly with less than two mixers.
            setFailsafe();
            continue;
        }

        AlarmsClear(SYSTEMALARMS_ALARM_ACTUATOR);

        float curve1 = 0.0f; // curve 1 is the throttle curve applied to all motors.
        float curve2 = 0.0f;

        // Interpolate curve 1 from throttleDesired as input.
        // assume reversible motor/mixer initially. We can later reverse this. The difference is simply that -ve throttleDesired values
        // map differently
        curve1 = MixerCurveFullRangeProportional(throttleDesired, mixerSettings.ThrottleCurve1, MIXERSETTINGS_THROTTLECURVE1_NUMELEM, multirotor);

        // The source for the secondary curve is selectable
        AccessoryDesiredData accessory;
        uint8_t curve2Source = mixerSettings.Curve2Source;
        switch (curve2Source) {
        case MIXERSETTINGS_CURVE2SOURCE_THROTTLE:
            // assume reversible motor/mixer initially
            curve2 = MixerCurveFullRangeProportional(throttleDesired, mixerSettings.ThrottleCurve2, MIXERSETTINGS_THROTTLECURVE2_NUMELEM, multirotor);
            break;
        case MIXERSETTINGS_CURVE2SOURCE_ROLL:
            // Throttle curve contribution the same for +ve vs -ve roll
            if (multirotor) {
                curve2 = MixerCurveFullRangeProportional(desired.Roll, mixerSettings.ThrottleCurve2, MIXERSETTINGS_THROTTLECURVE2_NUMELEM, multirotor);
            } else {
                curve2 = MixerCurveFullRangeAbsolute(desired.Roll, mixerSettings.ThrottleCurve2, MIXERSETTINGS_THROTTLECURVE2_NUMELEM, multirotor);
            }
            break;
        case MIXERSETTINGS_CURVE2SOURCE_PITCH:
            // Throttle curve contribution the same for +ve vs -ve pitch
            if (multirotor) {
                curve2 = MixerCurveFullRangeProportional(desired.Pitch, mixerSettings.ThrottleCurve2,
                                                         MIXERSETTINGS_THROTTLECURVE2_NUMELEM, multirotor);
            } else {
                curve2 = MixerCurveFullRangeAbsolute(desired.Pitch, mixerSettings.ThrottleCurve2,
                                                     MIXERSETTINGS_THROTTLECURVE2_NUMELEM, multirotor);
            }
            break;
        case MIXERSETTINGS_CURVE2SOURCE_YAW:
            // Throttle curve contribution the same for +ve vs -ve yaw
            if (multirotor) {
                curve2 = MixerCurveFullRangeProportional(desired.Yaw, mixerSettings.ThrottleCurve2, MIXERSETTINGS_THROTTLECURVE2_NUMELEM, multirotor);
            } else {
                curve2 = MixerCurveFullRangeAbsolute(desired.Yaw, mixerSettings.ThrottleCurve2, MIXERSETTINGS_THROTTLECURVE2_NUMELEM, multirotor);
            }
            break;
        case MIXERSETTINGS_CURVE2SOURCE_COLLECTIVE:
            // assume reversible motor/mixer initially
            curve2 = MixerCurveFullRangeProportional(collectiveDesired, mixerSettings.ThrottleCurve2,
                                                     MIXERSETTINGS_THROTTLECURVE2_NUMELEM, multirotor);
            break;
        case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY0:
        case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY1:
        case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY2:
        case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY3:
        case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY4:
        case MIXERSETTINGS_CURVE2SOURCE_ACCESSORY5:
            if (AccessoryDesiredInstGet(mixerSettings.Curve2Source - MIXERSETTINGS_CURVE2SOURCE_ACCESSORY0, &accessory) == 0) {
                // Throttle curve contribution the same for +ve vs -ve accessory....maybe not want we want.
                curve2 = MixerCurveFullRangeAbsolute(accessory.AccessoryVal, mixerSettings.ThrottleCurve2, MIXERSETTINGS_THROTTLECURVE2_NUMELEM, multirotor);
            } else {
                curve2 = 0.0f;
            }
            break;
        default:
            curve2 = 0.0f;
            break;
        }

        float *status   = (float *)&mixerStatus; // access status objects as an array of floats
        Mixer_t *mixers = (Mixer_t *)&mixerSettings.Mixer1Type;
        float maxMotor  = -1.0f; // highest motor value. Addition method needs this to be -1.0f, division method needs this to be 1.0f
        float minMotor  = 1.0f; // lowest motor value Addition method needs this to be 1.0f, division method needs this to be -1.0f

        for (int ct = 0; ct < MAX_MIX_ACTUATORS; ct++) {
            // During boot all camera actuators should be completely disabled (PWM pulse = 0).
            // command.Channel[i] is reused below as a channel PWM activity flag:
            // 0 - PWM disabled, >0 - PWM set to real mixer value using scaleChannel() later.
            // Setting it to 1 by default means "Rescale this channel and enable PWM on its output".
            command.Channel[ct] = 1;

            uint8_t mixer_type = mixers[ct].type;

            if (mixer_type == MIXERSETTINGS_MIXER1TYPE_DISABLED) {
                // Set to minimum if disabled.  This is not the same as saying PWM pulse = 0 us
                status[ct] = -1;
                continue;
            }

            if ((mixer_type == MIXERSETTINGS_MIXER1TYPE_MOTOR)) {
                float nonreversible_curve1 = curve1;
                float nonreversible_curve2 = curve2;
                if (nonreversible_curve1 < 0.0f) {
                    nonreversible_curve1 = 0.0f;
                }
                if (nonreversible_curve2 < 0.0f) {
                    if (!multirotor) { // allow negative throttle if multirotor. function scaleMotors handles the sanity checks.
                        nonreversible_curve2 = 0.0f;
                    }
                }
                status[ct] = ProcessMixer(ct, nonreversible_curve1, nonreversible_curve2, &desired, multirotor, fixedwing);
                // If not armed or motors aren't meant to spin all the time
                if (!armed ||
                    (!spinWhileArmed && !positiveThrottle)) {
                    status[ct] = -1; // force min throttle
                }
                // If armed meant to keep spinning,
                else if ((spinWhileArmed && !positiveThrottle) ||
                         (status[ct] < 0)) {
                    if (!multirotor) {
                        status[ct] = 0;
                        // allow throttle values lower than 0 if multirotor.
                        // Values will be scaled to 0 if they need to be in the scaleMotor function
                    }
                }
            } else if (mixer_type == MIXERSETTINGS_MIXER1TYPE_REVERSABLEMOTOR) {
                status[ct] = ProcessMixer(ct, curve1, curve2, &desired, multirotor, fixedwing);
                // Reversable Motors are like Motors but go to neutral instead of minimum
                // If not armed or motor is inactive - no "spinwhilearmed" for this engine type
                if (!armed || !activeThrottle) {
                    status[ct] = 0; // force neutral throttle
                }
            } else if (mixer_type == MIXERSETTINGS_MIXER1TYPE_SERVO) {
                status[ct] = ProcessMixer(ct, curve1, curve2, &desired, multirotor, fixedwing);
            } else {
                status[ct] = -1;

                // If an accessory channel is selected for direct bypass mode
                // In this configuration the accessory channel is scaled and mapped
                // directly to output.  Note: THERE IS NO SAFETY CHECK HERE FOR ARMING
                // these also will not be updated in failsafe mode.  I'm not sure what
                // the correct behavior is since it seems domain specific.  I don't love
                // this code
                if ((mixer_type >= MIXERSETTINGS_MIXER1TYPE_ACCESSORY0) &&
                    (mixer_type <= MIXERSETTINGS_MIXER1TYPE_ACCESSORY5)) {
                    if (AccessoryDesiredInstGet(mixer_type - MIXERSETTINGS_MIXER1TYPE_ACCESSORY0, &accessory) == 0) {
                        status[ct] = accessory.AccessoryVal;
                    } else {
                        status[ct] = -1;
                    }
                }

                if ((mixer_type >= MIXERSETTINGS_MIXER1TYPE_CAMERAROLLORSERVO1) &&
                    (mixer_type <= MIXERSETTINGS_MIXER1TYPE_CAMERAYAW)) {
                    if (camStabEnabled) {
                        CameraDesiredData cameraDesired;
                        CameraDesiredGet(&cameraDesired);
                        switch (mixer_type) {
                        case MIXERSETTINGS_MIXER1TYPE_CAMERAROLLORSERVO1:
                            status[ct] = cameraDesired.RollOrServo1;
                            break;
                        case MIXERSETTINGS_MIXER1TYPE_CAMERAPITCHORSERVO2:
                            status[ct] = cameraDesired.PitchOrServo2;
                            break;
                        case MIXERSETTINGS_MIXER1TYPE_CAMERAYAW:
                            status[ct] = cameraDesired.Yaw;
                            break;
                        default:
                            break;
                        }
                    } else {
                        status[ct] = -1;
                    }

                    // Disable camera actuators for CAMERA_BOOT_DELAY_MS after boot
                    if (thisSysTime < (CAMERA_BOOT_DELAY_MS / portTICK_RATE_MS)) {
                        command.Channel[ct] = 0;
                    }
                }

                if (mixer_type == MIXERSETTINGS_MIXER1TYPE_CAMERATRIGGER) {
                    if (camControlEnabled) {
                        CameraDesiredTriggerGet(&status[ct]);
                    } else {
                        status[ct] = 0;
                    }
                }
            }

            // If mixer type is motor we need to find which motor has the highest value and which motor has the lowest value.
            // For use in function scaleMotor
            if (mixers[ct].type == MIXERSETTINGS_MIXER1TYPE_MOTOR) {
                if (maxMotor < status[ct]) {
                    maxMotor = status[ct];
                }
                if (minMotor > status[ct]) {
                    minMotor = status[ct];
                }
            }
        }

        // Set real actuator output values scaling them from mixers. All channels
        // will be set except explicitly disabled (which will have PWM pulse = 0).
        for (int i = 0; i < MAX_MIX_ACTUATORS; i++) {
            if (command.Channel[i]) {
                if (mixers[i].type == MIXERSETTINGS_MIXER1TYPE_MOTOR) { // If mixer is for a motor we need to find the highest value of all motors
                    command.Channel[i] = scaleMotor(status[i],
                                                    actuatorSettings.ChannelMax[i],
                                                    actuatorSettings.ChannelMin[i],
                                                    actuatorSettings.ChannelNeutral[i],
                                                    maxMotor,
                                                    minMotor,
                                                    armed,
                                                    alwaysStabilizeWhenArmed,
                                                    throttleDesired);
                } else { // else we scale the channel
                    command.Channel[i] = scaleChannel(status[i],
                                                      actuatorSettings.ChannelMax[i],
                                                      actuatorSettings.ChannelMin[i],
                                                      actuatorSettings.ChannelNeutral[i]);
                }
            }
        }

        // Store update time
        command.UpdateTime = dTMilliseconds;
        if (command.UpdateTime > command.MaxUpdateTime) {
            command.MaxUpdateTime = command.UpdateTime;
        }
        // Update output object
        ActuatorCommandSet(&command);
        // Update in case read only (eg. during servo configuration)
        ActuatorCommandGet(&command);

#ifdef DIAG_MIXERSTATUS
        MixerStatusSet(&mixerStatus);
#endif


        // Update servo outputs
        bool success = true;

        for (int n = 0; n < ACTUATORCOMMAND_CHANNEL_NUMELEM; ++n) {
            success &= set_channel(n, command.Channel[n]);
        }

        PIOS_Servo_Update();

        if (!success) {
            command.NumFailedUpdates++;
            ActuatorCommandSet(&command);
            AlarmsSet(SYSTEMALARMS_ALARM_ACTUATOR, SYSTEMALARMS_ALARM_CRITICAL);
        }
#ifdef PIOS_INCLUDE_INSTRUMENTATION
        PIOS_Instrumentation_TimeEnd(counter);
#endif
    }
}


/**
 * Process mixing for one actuator
 */
float ProcessMixer(const int index, const float curve1, const float curve2,
                   ActuatorDesiredData *desired, bool multirotor, bool fixedwing)
{
    const Mixer_t *mixers = (Mixer_t *)&mixerSettings.Mixer1Type; // pointer to array of mixers in UAVObjects
    const Mixer_t *mixer  = &mixers[index];
    float differential    = 1.0f;

    // Apply differential only for fixedwing and Roll servos
    if (fixedwing && (mixerSettings.FirstRollServo > 0) &&
        (mixer->type == MIXERSETTINGS_MIXER1TYPE_SERVO) &&
        (mixer->matrix[MIXERSETTINGS_MIXER1VECTOR_ROLL] != 0)) {
        // Positive differential
        if (mixerSettings.RollDifferential > 0) {
            // Check for first Roll servo (should be left aileron or elevon) and Roll desired (positive/negative)
            if (((index == mixerSettings.FirstRollServo - 1) && (desired->Roll > 0.0f))
                || ((index != mixerSettings.FirstRollServo - 1) && (desired->Roll < 0.0f))) {
                differential -= (mixerSettings.RollDifferential * 0.01f);
            }
        } else if (mixerSettings.RollDifferential < 0) {
            if (((index == mixerSettings.FirstRollServo - 1) && (desired->Roll < 0.0f))
                || ((index != mixerSettings.FirstRollServo - 1) && (desired->Roll > 0.0f))) {
                differential -= (-mixerSettings.RollDifferential * 0.01f);
            }
        }
    }

    float result = ((((float)mixer->matrix[MIXERSETTINGS_MIXER1VECTOR_THROTTLECURVE1]) * curve1) +
                    (((float)mixer->matrix[MIXERSETTINGS_MIXER1VECTOR_THROTTLECURVE2]) * curve2) +
                    (((float)mixer->matrix[MIXERSETTINGS_MIXER1VECTOR_ROLL]) * desired->Roll * differential) +
                    (((float)mixer->matrix[MIXERSETTINGS_MIXER1VECTOR_PITCH]) * desired->Pitch) +
                    (((float)mixer->matrix[MIXERSETTINGS_MIXER1VECTOR_YAW]) * desired->Yaw)) / 128.0f;

    if (mixer->type == MIXERSETTINGS_MIXER1TYPE_MOTOR) {
        if (!multirotor) { // we allow negative throttle with a multirotor
            if (result < 0.0f) { // zero throttle
                result = 0.0f;
            }
        }
    }

    return result;
}


/**
 * Interpolate a throttle curve
 * Full range input (-1 to 1) for yaw, roll, pitch
 * Output range (-1 to 1) reversible motor/throttle curve
 *
 * Input of -1 -> -lookup(1)
 * Input of 0  ->  lookup(0)
 * Input of 1  ->  lookup(1)
 */
static float MixerCurveFullRangeProportional(const float input, const float *curve, uint8_t elements, bool multirotor)
{
    float unsigned_value = MixerCurveFullRangeAbsolute(input, curve, elements, multirotor);

    if (input < 0.0f) {
        return -unsigned_value;
    } else {
        return unsigned_value;
    }
}

/**
 * Interpolate a throttle curve
 * Full range input (-1 to 1) for yaw, roll, pitch
 * Output range (0 to 1) non-reversible motor/throttle curve
 *
 * Input of -1 -> lookup(1)
 * Input of 0  -> lookup(0)
 * Input of 1  -> lookup(1)
 */
static float MixerCurveFullRangeAbsolute(const float input, const float *curve, uint8_t elements, bool multirotor)
{
    float abs_input = fabsf(input);
    float scale     = abs_input * (float)(elements - 1);
    int idx1 = scale;

    scale -= (float)idx1; // remainder
    if (curve[0] < -1) {
        return abs_input;
    }
    int idx2 = idx1 + 1;
    if (idx2 >= elements) {
        idx2 = elements - 1; // clamp to highest entry in table
        if (idx1 >= elements) {
            if (multirotor) {
                // if multirotor frame we can return throttle values higher than 100%.
                // Since the we don't have elements in the curve higher than 100% we return
                // the last element multiplied by the throttle float
                if (input < 2.0f) { // this limits positive throttle to 200% of max value in table (Maybe this is too much allowance)
                    return curve[idx2] * input;
                } else {
                    return curve[idx2] * 2.0f; // return 200% of max value in table
                }
            }
            idx1 = elements - 1;
        }
    }

    float unsigned_value = curve[idx1] * (1.0f - scale) + curve[idx2] * scale;
    return unsigned_value;
}


/**
 * Convert channel from -1/+1 to servo pulse duration in microseconds
 */
static int16_t scaleChannel(float value, int16_t max, int16_t min, int16_t neutral)
{
    int16_t valueScaled;

    // Scale
    if (value >= 0.0f) {
        valueScaled = (int16_t)(value * ((float)(max - neutral))) + neutral;
    } else {
        valueScaled = (int16_t)(value * ((float)(neutral - min))) + neutral;
    }

    if (max > min) {
        if (valueScaled > max) {
            valueScaled = max;
        }
        if (valueScaled < min) {
            valueScaled = min;
        }
    } else {
        if (valueScaled < max) {
            valueScaled = max;
        }
        if (valueScaled > min) {
            valueScaled = min;
        }
    }

    return valueScaled;
}

/**
 * Move and compress all motor outputs so that none goes below neutral,
 * and all motors are below or equal to max.
 */
static inline int16_t scaleMotorMoveAndCompress(float valueMotor, int16_t max, int16_t neutral, float maxMotor, float minMotor)
{
    // The valueMotor parameter is the desired motor value somewhere in the
    // [minMotor, maxMotor] range, which is [< -1.00, > 1.00].
    //
    // Before converting valueMotor to the [neutral, max] range, we scale
    // valueMotor to a value in the [0.0f, 1.0f] range.
    //
    // This is done by, first, conceptually moving all three values valueMotor,
    // minMotor, and maxMotor, equally so that the [minMotor, maxMotor] range,
    // are contained or overlaps with the [0.0f, 1.0f] range.
    //
    // Then if the [minMotor, maxMotor] range is larger than 1.0f, the values
    // are compressed enough to shrink the [minMotor + move, maxMotor + move]
    // range to fit within the [0.0f, 1.0f] range.

    // First move the values so that the source range [minMotor, maxMotor]
    // covers the target range [0.0f, 1.0f] as much as possible.
    float moveValue = 0.0f;

    if (minMotor <= 0.0f) {
        // Negative minMotor always adjust to 0.
        moveValue = -minMotor;
    } else if (maxMotor > 1.0f) {
        // A too large maxMotor value adjust the range down towards, but not past, the minMotor value.
        float beyondMax = maxMotor - 1.0f;
        moveValue = -(beyondMax < minMotor ? beyondMax : minMotor);
    }

    // Then calculate the compress value, if the source range is greater than 1.0f.
    float compressValue = 1.0f;

    float rangeMotor    = maxMotor - minMotor;
    if (rangeMotor > 1.0f) {
        compressValue = rangeMotor;
    }

    // Combine the movement and compression, to get the value within [0.0f, 1.0f]
    float movedAndCompressedValue = (valueMotor + moveValue) / compressValue;

    // And last, convert the value into the [neutral, max] range.
    int16_t valueScaled = movedAndCompressedValue * ((float)(max - neutral)) + neutral;

    if (valueScaled > max) {
        valueScaled = max; // clamp to max value only after scaling is done.
    }

    PIOS_Assert(valueScaled >= neutral);

    return valueScaled;
}

/**
 * Constrain motor values to keep any one motor value from going too far out of range of another motor
 */
static int16_t scaleMotor(float value, int16_t max, int16_t min, int16_t neutral, float maxMotor, float minMotor, bool armed, bool alwaysStabilizeWhenArmed, float throttleDesired)
{
    int16_t valueScaled;

    if (max > min) {
        valueScaled = scaleMotorMoveAndCompress(value, max, neutral, maxMotor, minMotor);
    } else {
        // not sure what to do about reversed polarity right now. Why would anyone do this?
        valueScaled = scaleChannel(value, max, min, neutral);
    }

    // I've added the bool alwaysStabilizeWhenArmed to this function. Right now we command the motors at min or a range between neutral and max.
    // NEVER should a motor be command at between min and neutral. I don't like the idea of stabilization ever commanding a motor to min, but we give people the option
    // This prevents motors startup sync issues causing possible ESC failures.

    // safety checks
    if (!armed) {
        // if not armed return min EVERYTIME!
        valueScaled = min;
    } else if (!alwaysStabilizeWhenArmed && (throttleDesired <= 0.0f) && spinWhileArmed) {
        // all motors idle is alwaysStabilizeWhenArmed is false, throttle is less than or equal to neutral and spin while armed
        // stabilize when armed?
        valueScaled = neutral;
    } else if (!spinWhileArmed && (throttleDesired <= 0.0f)) {
        // soft disarm
        valueScaled = min;
    }

    return valueScaled;
}

/**
 * Set actuator output to the neutral values (failsafe)
 */
static void setFailsafe()
{
    /* grab only the parts that we are going to use */
    int16_t Channel[ACTUATORCOMMAND_CHANNEL_NUMELEM];

    ActuatorCommandChannelGet(Channel);

    const Mixer_t *mixers = (Mixer_t *)&mixerSettings.Mixer1Type; // pointer to array of mixers in UAVObjects

    // Reset ActuatorCommand to safe values
    for (int n = 0; n < ACTUATORCOMMAND_CHANNEL_NUMELEM; ++n) {
        if (mixers[n].type == MIXERSETTINGS_MIXER1TYPE_MOTOR) {
            Channel[n] = actuatorSettings.ChannelMin[n];
        } else if (mixers[n].type == MIXERSETTINGS_MIXER1TYPE_SERVO || mixers[n].type == MIXERSETTINGS_MIXER1TYPE_REVERSABLEMOTOR) {
            // reversible motors need calibration wizard that allows channel neutral to be the 0 velocity point
            Channel[n] = actuatorSettings.ChannelNeutral[n];
        } else {
            Channel[n] = 0;
        }
    }

    // Set alarm
    AlarmsSet(SYSTEMALARMS_ALARM_ACTUATOR, SYSTEMALARMS_ALARM_CRITICAL);

    // Update servo outputs
    for (int n = 0; n < ACTUATORCOMMAND_CHANNEL_NUMELEM; ++n) {
        set_channel(n, Channel[n]);
    }
    // Send the updated command
    PIOS_Servo_Update();

    // Update output object's parts that we changed
    ActuatorCommandChannelSet(Channel);
}

/**
 * determine buzzer or blink sequence
 **/

typedef enum { BUZZ_BUZZER = 0, BUZZ_ARMING = 1, BUZZ_INFO = 2, BUZZ_MAX = 3 } buzzertype;

static inline bool buzzerState(buzzertype type)
{
    // This is for buzzers that take a PWM input

    static uint32_t tune[BUZZ_MAX] = { 0 };
    static uint32_t tunestate[BUZZ_MAX] = { 0 };


    uint32_t newTune = 0;

    if (type == BUZZ_BUZZER) {
        // Decide what tune to play
        if (AlarmsGet(SYSTEMALARMS_ALARM_BATTERY) > SYSTEMALARMS_ALARM_WARNING) {
            newTune = 0b11110110110000; // pause, short, short, short, long
        } else if (AlarmsGet(SYSTEMALARMS_ALARM_GPS) >= SYSTEMALARMS_ALARM_WARNING) {
            newTune = 0x80000000; // pause, short
        } else {
            newTune = 0;
        }
    } else { // BUZZ_ARMING || BUZZ_INFO
        uint8_t arming;
        FlightStatusArmedGet(&arming);
        // base idle tune
        newTune = 0x80000000; // 0b1000...

        // Merge the error pattern for InfoLed
        if (type == BUZZ_INFO) {
            if (AlarmsGet(SYSTEMALARMS_ALARM_BATTERY) > SYSTEMALARMS_ALARM_WARNING) {
                newTune |= 0b00000000001111111011111110000000;
            } else if (AlarmsGet(SYSTEMALARMS_ALARM_GPS) >= SYSTEMALARMS_ALARM_WARNING) {
                newTune |= 0b00000000000000110110110000000000;
            }
        }
        // fast double blink pattern if armed
        if (arming == FLIGHTSTATUS_ARMED_ARMED) {
            newTune |= 0xA0000000; // 0b101000...
        }
    }

    // Do we need to change tune?
    if (newTune != tune[type]) {
        tune[type] = newTune;
        // resynchronize all tunes on change, so they stay in sync
        for (int i = 0; i < BUZZ_MAX; i++) {
            tunestate[i] = tune[i];
        }
    }

    // Play tune
    bool buzzOn     = false;
    static portTickType lastSysTime = 0;
    portTickType thisSysTime = xTaskGetTickCount();
    portTickType dT = 0;

    // For now, only look at the battery alarm, because functions like AlarmsHasCritical() can block for some time; to be discussed
    if (tune[type]) {
        if (thisSysTime > lastSysTime) {
            dT = thisSysTime - lastSysTime;
        } else {
            lastSysTime = 0; // avoid the case where SysTimeMax-lastSysTime <80
        }

        buzzOn = (tunestate[type] & 1);

        if (dT > 80) {
            // Go to next bit in alarm_seq_state
            for (int i = 0; i < BUZZ_MAX; i++) {
                tunestate[i] >>= 1;
                if (tunestate[i] == 0) { // All done, re-start the tune
                    tunestate[i] = tune[i];
                }
            }
            lastSysTime = thisSysTime;
        }
    }
    return buzzOn;
}


#if defined(ARCH_POSIX) || defined(ARCH_WIN32)
static bool set_channel(uint8_t mixer_channel, uint16_t value)
{
    return true;
}
#else
static bool set_channel(uint8_t mixer_channel, uint16_t value)
{
    switch (actuatorSettings.ChannelType[mixer_channel]) {
    case ACTUATORSETTINGS_CHANNELTYPE_PWMALARMBUZZER:
        PIOS_Servo_Set(actuatorSettings.ChannelAddr[mixer_channel],
                       buzzerState(BUZZ_BUZZER) ? actuatorSettings.ChannelMax[mixer_channel] : actuatorSettings.ChannelMin[mixer_channel]);
        return true;

    case ACTUATORSETTINGS_CHANNELTYPE_ARMINGLED:
        PIOS_Servo_Set(actuatorSettings.ChannelAddr[mixer_channel],
                       buzzerState(BUZZ_ARMING) ? actuatorSettings.ChannelMax[mixer_channel] : actuatorSettings.ChannelMin[mixer_channel]);
        return true;

    case ACTUATORSETTINGS_CHANNELTYPE_INFOLED:
        PIOS_Servo_Set(actuatorSettings.ChannelAddr[mixer_channel],
                       buzzerState(BUZZ_INFO) ? actuatorSettings.ChannelMax[mixer_channel] : actuatorSettings.ChannelMin[mixer_channel]);
        return true;

    case ACTUATORSETTINGS_CHANNELTYPE_PWM:
    {
        uint8_t mode = pinsMode[actuatorSettings.ChannelAddr[mixer_channel]];
        switch (mode) {
        case ACTUATORSETTINGS_BANKMODE_ONESHOT125:
            // Remap 1000-2000 range to 125-250µs
            PIOS_Servo_Set(actuatorSettings.ChannelAddr[mixer_channel], value * ACTUATOR_ONESHOT125_PULSE_FACTOR);
            break;
        case ACTUATORSETTINGS_BANKMODE_ONESHOT42:
            // Remap 1000-2000 range to 41,666-83,333µs
            PIOS_Servo_Set(actuatorSettings.ChannelAddr[mixer_channel], value * ACTUATOR_ONESHOT42_PULSE_FACTOR);
            break;
        case ACTUATORSETTINGS_BANKMODE_MULTISHOT:
            // Remap 1000-2000 range to 5-25µs
            PIOS_Servo_Set(actuatorSettings.ChannelAddr[mixer_channel], (value * ACTUATOR_MULTISHOT_PULSE_FACTOR) - 180);
            break;
        case ACTUATORSETTINGS_BANKMODE_DSHOT:
            // Remap 0-2000 range to: 0 = disarmed, 1 to 47 = Reserved for special commands, 48 to 2047 = Active throttle control.
            if (value > 0) {
                value += 47; /* skip over reserved values */
            }
            PIOS_Servo_Set(actuatorSettings.ChannelAddr[mixer_channel], value);
            break;
        default:
            PIOS_Servo_Set(actuatorSettings.ChannelAddr[mixer_channel], value);
            break;
        }
        return true;
    }

#if defined(PIOS_INCLUDE_I2C_ESC)
    case ACTUATORSETTINGS_CHANNELTYPE_MK:
        return PIOS_SetMKSpeed(actuatorSettings->ChannelAddr[mixer_channel], value);

    case ACTUATORSETTINGS_CHANNELTYPE_ASTEC4:
        return PIOS_SetAstec4Speed(actuatorSettings->ChannelAddr[mixer_channel], value);

#endif
    default:
        return false;
    }

    return false;
}
#endif /* if defined(ARCH_POSIX) || defined(ARCH_WIN32) */

/**
 * @brief Update the servo update rate
 */
static void actuator_update_rate_if_changed(bool force_update)
{
    static uint16_t prevBankUpdateFreq[ACTUATORSETTINGS_BANKUPDATEFREQ_NUMELEM];
    static uint8_t prevBankMode[ACTUATORSETTINGS_BANKMODE_NUMELEM];
    static uint16_t prevDShotMode;
    bool updateMode = force_update || (memcmp(prevBankMode, actuatorSettings.BankMode, sizeof(prevBankMode)) != 0);
    bool updateFreq = force_update || (memcmp(prevBankUpdateFreq, actuatorSettings.BankUpdateFreq, sizeof(prevBankUpdateFreq)) != 0);

    if (force_update || (prevDShotMode != actuatorSettings.DShotMode)) {
        PIOS_Servo_DSHot_Rate(actuatorSettings.DShotMode);
        prevDShotMode = actuatorSettings.DShotMode;
    }

    // check if any setting is changed
    if (updateMode || updateFreq) {
        /* Something has changed, apply the settings to HW */

        uint16_t freq[ACTUATORSETTINGS_BANKUPDATEFREQ_NUMELEM];
        uint32_t clock[ACTUATORSETTINGS_BANKUPDATEFREQ_NUMELEM] = { 0 };
        for (uint8_t i = 0; i < ACTUATORSETTINGS_BANKMODE_NUMELEM; i++) {
            enum pios_servo_bank_mode servo_bank_mode = PIOS_SERVO_BANK_MODE_PWM;

            switch (actuatorSettings.BankMode[i]) {
            case ACTUATORSETTINGS_BANKMODE_ONESHOT125:
            case ACTUATORSETTINGS_BANKMODE_ONESHOT42:
            case ACTUATORSETTINGS_BANKMODE_MULTISHOT:
                freq[i]  = 100; // Value must be small enough so CCr isn't update until the PIOS_Servo_Update is triggered
                clock[i] = ACTUATOR_ONESHOT_CLOCK; // Setup an 12MHz timer clock
                servo_bank_mode = PIOS_SERVO_BANK_MODE_SINGLE_PULSE;
                break;
            case ACTUATORSETTINGS_BANKMODE_PWMSYNC:
                freq[i]  = 100;
                clock[i] = ACTUATOR_PWM_CLOCK;
                servo_bank_mode = PIOS_SERVO_BANK_MODE_SINGLE_PULSE;
                break;
            case ACTUATORSETTINGS_BANKMODE_DSHOT:
                freq[i]  = 100;
                clock[i] = ACTUATOR_PWM_CLOCK;
                servo_bank_mode = PIOS_SERVO_BANK_MODE_DSHOT;
                break;
            default: // PWM
                freq[i]  = actuatorSettings.BankUpdateFreq[i];
                clock[i] = ACTUATOR_PWM_CLOCK;
                servo_bank_mode = PIOS_SERVO_BANK_MODE_PWM;
                break;
            }

            if (force_update || (actuatorSettings.BankMode[i] != prevBankMode[i])) {
                PIOS_Servo_SetBankMode(i, servo_bank_mode);
            }
        }

        memcpy(prevBankMode,
               actuatorSettings.BankMode,
               sizeof(prevBankMode));

        PIOS_Servo_SetHz(freq, clock, ACTUATORSETTINGS_BANKUPDATEFREQ_NUMELEM);

        memcpy(prevBankUpdateFreq,
               actuatorSettings.BankUpdateFreq,
               sizeof(prevBankUpdateFreq));
        // retrieve mode from related bank
        for (uint8_t i = 0; i < MAX_MIX_ACTUATORS; i++) {
            uint8_t bank = PIOS_Servo_GetPinBank(i);
            pinsMode[i] = actuatorSettings.BankMode[bank];
        }
    }
}

static void update_servo_active()
{
    /* For each mixer output that is not disabled,
     * figure out servo address and send allocation map to pios_servo driver.
     * We need to execute this when either ActuatorSettings or MixerSettings change.
     */
    uint32_t servo_active = 0;

    Mixer_t *mixers = (Mixer_t *)&mixerSettings.Mixer1Type;

    for (int ct = 0; ct < MAX_MIX_ACTUATORS; ct++) {
        if (mixers[ct].type != MIXERSETTINGS_MIXER1TYPE_DISABLED) {
            servo_active |= 1 << actuatorSettings.ChannelAddr[ct];
        }
    }

    PIOS_Servo_SetActive(servo_active);
}

static void ActuatorSettingsUpdatedCb(__attribute__((unused)) UAVObjEvent *ev)
{
    ActuatorSettingsGet(&actuatorSettings);
    spinWhileArmed = actuatorSettings.MotorsSpinWhileArmed == ACTUATORSETTINGS_MOTORSSPINWHILEARMED_TRUE;
    if (frameType == FRAME_TYPE_GROUND) {
        spinWhileArmed = false;
    }
    actuator_update_rate_if_changed(false);

    update_servo_active();
}

static void MixerSettingsUpdatedCb(__attribute__((unused)) UAVObjEvent *ev)
{
    MixerSettingsGet(&mixerSettings);
    mixer_settings_count = 0;
    Mixer_t *mixers = (Mixer_t *)&mixerSettings.Mixer1Type;
    for (int ct = 0; ct < MAX_MIX_ACTUATORS; ct++) {
        if (mixers[ct].type != MIXERSETTINGS_MIXER1TYPE_DISABLED) {
            mixer_settings_count++;
        }
    }

    update_servo_active();
}
static void SettingsUpdatedCb(__attribute__((unused)) UAVObjEvent *ev)
{
    frameType = GetCurrentFrameType();
#ifndef PIOS_EXCLUDE_ADVANCED_FEATURES
    uint8_t TreatCustomCraftAs;
    VtolPathFollowerSettingsTreatCustomCraftAsGet(&TreatCustomCraftAs);

    if (frameType == FRAME_TYPE_CUSTOM) {
        switch (TreatCustomCraftAs) {
        case VTOLPATHFOLLOWERSETTINGS_TREATCUSTOMCRAFTAS_FIXEDWING:
            frameType = FRAME_TYPE_FIXED_WING;
            break;
        case VTOLPATHFOLLOWERSETTINGS_TREATCUSTOMCRAFTAS_VTOL:
            frameType = FRAME_TYPE_MULTIROTOR;
            break;
        case VTOLPATHFOLLOWERSETTINGS_TREATCUSTOMCRAFTAS_GROUND:
            frameType = FRAME_TYPE_GROUND;
            break;
        }
    }
#endif

    SystemSettingsThrustControlGet(&thrustType);
}

/**
 * @}
 * @}
 */