/*
 * AP_SBusOut.cpp
 *
 *  Created on: Aug 19, 2017
 *      Author: Mark Whitehorn
 *
 * method sbus1_out was ported from ardupilot/modules/PX4Firmware/src/lib/rc/sbus.c
 * which has the following license:
 *
 *   Copyright (c) 2012-2014 PX4 Development Team. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name PX4 nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */
#include "AP_SBusOut.h"
#include <AP_Math/AP_Math.h>
#include <AP_SerialManager/AP_SerialManager.h>
#include <SRV_Channel/SRV_Channel.h>

extern const AP_HAL::HAL& hal;

#define SBUS_DEBUG 0

// SBUS1 constant definitions
// pulse widths measured using FrSky Sbus/PWM converter
#define SBUS_BSIZE    25
#define SBUS_CHANNELS 16
#define SBUS_MIN 880.0f
#define SBUS_MAX 2156.0f
#define SBUS_SCALE (2048.0f / (SBUS_MAX - SBUS_MIN))

const AP_Param::GroupInfo AP_SBusOut::var_info[] = {
    // @Param: RATE
    // @DisplayName: SBUS default output rate
    // @Description: This sets the SBUS output frame rate in Hz.
    // @Range: 25 250
    // @User: Advanced
    // @Units: Hz
    AP_GROUPINFO("RATE",  1, AP_SBusOut, sbus_rate, 50),

    AP_GROUPEND
};


// constructor
AP_SBusOut::AP_SBusOut(void)
{
    // set defaults from the parameter table
    AP_Param::setup_object_defaults(this, var_info);
}

/*
  format a SBUS output frame into a 25 byte buffer
 */
void AP_SBusOut::sbus_format_frame(uint16_t *channels, uint8_t num_channels, uint8_t buffer[SBUS_BSIZE])
{
    uint8_t index = 1;
    uint8_t offset = 0;

    memset(buffer, 0, SBUS_BSIZE);
    buffer[0] = 0x0f;

    /* construct sbus frame representing channels 1 through 16 (max) */
    uint8_t nchan = MIN(num_channels, SBUS_CHANNELS);
    for (unsigned i = 0; i < nchan; ++i) {
        /*protect from out of bounds values and limit to 11 bits*/
        uint16_t pwmval = MAX(channels[i], SBUS_MIN);
        uint16_t value = (uint16_t)((pwmval - SBUS_MIN) * SBUS_SCALE);
        if (value > 0x07ff) {
            value = 0x07ff;
        }

#if SBUS_DEBUG
        static uint16_t lastch9 = 0;
        if ((i==8) && (pwmval != lastch9)) {
            lastch9 = pwmval;
            hal.console->printf("channel 9 pwm: %04d\n", pwmval);
        }
#endif

        while (offset >= 8) {
            ++index;
            offset -= 8;
        }

        buffer[index] |= (value << (offset)) & 0xff;
        buffer[index + 1] |= (value >> (8 - offset)) & 0xff;
        buffer[index + 2] |= (value >> (16 - offset)) & 0xff;
        offset += 11;
    }
}

/*
 * build and send sbus1 frame representing first 16 servo channels
 * input arg is pointer to uart
 */
void
AP_SBusOut::update()
{
    if (!initialised) {
        initialised = true;
        init();
    }

    if (sbus1_uart == nullptr) {
        return;
    }

    // constrain output rate using sbus_frame_interval
    static uint32_t last_micros = 0;
    uint32_t now = AP_HAL::micros();
    if ((now - last_micros) <= sbus_frame_interval) {
        return;
    }

    last_micros = now;

    /* construct sbus frame representing channels 1 through 16 (max) */
    uint8_t nchan = MIN(NUM_SERVO_CHANNELS, SBUS_CHANNELS);
    uint16_t channels[SBUS_CHANNELS] {};

    for (unsigned i = 0; i < nchan; ++i) {
        SRV_Channel *c = SRV_Channels::srv_channel(i);
        if (c == nullptr) {
            continue;
        }
        channels[i] = c->get_output_pwm();
    }
    uint8_t buffer[SBUS_BSIZE];
    sbus_format_frame(channels, nchan, buffer);

#if SBUS_DEBUG
    hal.gpio->pinMode(55, HAL_GPIO_OUTPUT);
    hal.gpio->write(55, 1);
#endif

    sbus1_uart->write(buffer, sizeof(buffer));

#if SBUS_DEBUG
    hal.gpio->pinMode(55, HAL_GPIO_OUTPUT);
    hal.gpio->write(55, 0);
#endif
}

void AP_SBusOut::init() {
    uint16_t rate = sbus_rate.get();

#if SBUS_DEBUG
    hal.console->printf("AP_SBusOut: init %d Hz\n", rate);
#endif

    // subtract 500usec from requested frame interval to allow for latency
    sbus_frame_interval = (1000UL * 1000UL) / rate - 500;
    // at 100,000 bps, a 300 bit sbus frame takes 3msec to transfer
    // require a minimum 700usec interframe gap
    if (sbus_frame_interval < 3700) {
        sbus_frame_interval = 3700;
    }

    AP_SerialManager *serial_manager = AP_SerialManager::get_singleton();
    if (!serial_manager) {
        return;
    }
    sbus1_uart = serial_manager->find_serial(AP_SerialManager::SerialProtocol_Sbus1,0);
}

