Skip to content

File RBCXSmartServo.cpp

File List > demo-projets > esp > RBCXSmartServo.cpp

Go to the documentation of this file.

#include <algorithm>
#include <chrono>
#include <esp_log.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <math.h>

#include "RBCXManager.h"
#include "RBCXSmartServo.h"

#define TAG "RBCXSmartServoBus"
#define MS_TO_TICKS(ms)                                                        \
    ((portTICK_PERIOD_MS <= ms) ? (ms / portTICK_PERIOD_MS) : 1)

namespace rb {

SmartServoBus::SmartServoBus() {}

void SmartServoBus::install(uint8_t servo_count) {
    if (!m_servos.empty() || servo_count == 0)
        return;

    m_servos.resize(servo_count);

    TaskHandle_t task;
    xTaskCreate(&SmartServoBus::regulatorRoutineTrampoline, "rbservo_reg", 1536,
        this, 2, &task);
    Manager::get().monitorTask(task);

    Angle val;
    for (uint8_t i = 0; i < servo_count; ++i) {
        for (int x = 0; x < 3; ++x) {
            val = pos(i);
            if (!val.isNaN()) {
                break;
            } else {
                ESP_LOGW(
                    TAG, "failed to read servo %d pos, attempt %d", i, x + 1);
            }
        }

        if (val.isNaN()) {
            ESP_LOGE(TAG,
                "failed to read position from servo %d, it will not work!", i);
            continue;
        }

        const uint16_t deg_val = 100 * val.deg();

        m_mutex.lock();
        m_servos[i].current = deg_val;
        m_servos[i].target = deg_val;
        m_mutex.unlock();
    }
}

void SmartServoBus::setId(uint8_t newId, uint8_t destId) {
    // TODO
}

uint8_t SmartServoBus::getId(uint8_t destId) {
    // TODO
    return 0;
}

void SmartServoBus::set(uint8_t id, Angle ang, float speed, float speed_raise) {
    speed = std::max(1.f, std::min(240.f, speed)) / 10.f;
    const uint16_t angle
        = std::max(0.f, std::min(360.f, (float)ang.deg())) * 100;

    std::lock_guard<std::mutex> lock(m_mutex);

    auto& si = m_servos[id];
    if (!si.hasValidCurrent()) {
        const auto cur = pos(id);
        if (cur.isNaN()) {
            ESP_LOGE(TAG, "failed to get servo %d position, can't move it!",
                int(id));
            return;
        }
        const uint16_t deg_val = 100 * cur.deg();
        si.current = deg_val;
        si.target = deg_val;
    }

    if (si.current == angle)
        return;

    if ((si.current > si.target) != (si.current > angle)) {
        si.speed_coef = 0.f;
    }

    si.target = angle;
    si.speed_target = speed;
    si.speed_raise = speed_raise;
}

Angle SmartServoBus::pos(uint8_t id) {
    // TODO
    return Angle::deg(0);
}

Angle SmartServoBus::posOffline(uint8_t id) {
    std::lock_guard<std::mutex> lock(m_mutex);
    auto& s = m_servos[id];
    if (s.current == 0xFFFF)
        return Angle::nan();
    return Angle::deg(Angle::_T(s.current) / 100.f);
}

void SmartServoBus::limit(uint8_t id, Angle bottom, Angle top) {
    // TODO
}

void SmartServoBus::setAutoStop(uint8_t id, bool enable) {
    m_mutex.lock();
    m_servos[id].auto_stop = enable;
    m_mutex.unlock();
}

void SmartServoBus::regulatorRoutineTrampoline(void* cookie) {
    ((SmartServoBus*)cookie)->regulatorRoutine();
}

void SmartServoBus::regulatorRoutine() {
    const size_t servos_cnt = m_servos.size();

    constexpr uint32_t msPerServo = 30;
    constexpr auto ticksPerServo = MS_TO_TICKS(msPerServo);
    const uint32_t msPerIter = servos_cnt * msPerServo;
    const auto ticksPerIter = MS_TO_TICKS(msPerIter);

    // TODO
    auto queue = xQueueCreate(1, 4 /*sizeof(struct rx_response)*/);
    while (true) {
        const auto tm_iter_start = xTaskGetTickCount();
        for (size_t i = 0; i < servos_cnt; ++i) {
            const auto tm_servo_start = xTaskGetTickCount();
            regulateServo(queue, i, msPerIter);
            const auto diff = xTaskGetTickCount() - tm_servo_start;
            if (diff < ticksPerServo) {
                vTaskDelay(ticksPerServo - diff);
            }
        }

        const auto diff = xTaskGetTickCount() - tm_iter_start;
        if (diff < ticksPerIter) {
            vTaskDelay(ticksPerIter - diff);
        }
    }
}

bool SmartServoBus::regulateServo(
    QueueHandle_t responseQueue, size_t id, uint32_t timeSliceMs) {
    float move_pos_deg;
    auto& s = m_servos[id];

    {
        std::lock_guard<std::mutex> lock(m_mutex);

        if (s.auto_stop) {
            // TODO
            /*lw::Packet pos_req(id, lw::Command::SERVO_POS_READ);
            send(pos_req, responseQueue, true);
            xQueueReceive(responseQueue, &resp, portMAX_DELAY);
            if (resp.size == 0x08) {
                const float val = (float)((resp.data[6] << 8) | resp.data[5]);
                const int val_int = (val / 1000.f) * 24000.f;
                const int diff = val_int - int(s.current);
                if (abs(diff) > 300) {
                    if (++s.auto_stop_counter > 5) {
                        s.target = val_int + (diff > 0 ? -200 : 200);
                        s.auto_stop_counter = 0;
                    }
                } else if (s.auto_stop_counter != 0) {
                    s.auto_stop_counter = 0;
                }
            }*/
        }

        if (s.current == s.target) {
            return false;
        }

        float speed = s.speed_target;
        if (s.speed_coef < 1.f) {
            s.speed_coef
                = std::min(1.f, s.speed_coef + (s.speed_raise * timeSliceMs));
            speed *= (s.speed_coef * s.speed_coef);
        }

        int32_t dist = abs(int32_t(s.target) - int32_t(s.current));
        dist = std::max(1, std::min(dist, int32_t(speed * timeSliceMs)));
        if (dist > 0) {
            if (s.target < s.current) {
                s.current -= dist;
            } else {
                s.current += dist;
            }
        }

        if (dist <= 0 || s.current == s.target) {
            s.current = s.target;
            s.speed_coef = 0.f;
        }
        move_pos_deg = float(s.current) / 100.f;
    }

    // TODO
    /*const auto pkt = lw::Servo::move(id, Angle::deg(move_pos_deg),
        std::chrono::milliseconds(timeSliceMs - 5));
    send(pkt, responseQueue, false, true);

    if (xQueueReceive(responseQueue, &resp, 500 / portTICK_PERIOD_MS)
        != pdTRUE) {
        ESP_LOGE(TAG, "Response to move packet not received!");
    }*/
    return true;
}

}; // namespace rb