Add messages to operation endpoint
This commit is contained in:
parent
207501a894
commit
a9b36770dc
@ -13,6 +13,11 @@ enum Error {
|
||||
NoError = 0;
|
||||
|
||||
UsedPowerChannelNotActive = 1;
|
||||
MaxNumberOfChannels = 2;
|
||||
ControlChannelUsedMultipleTimes = 3;
|
||||
ChannelNotFound = 4;
|
||||
ControlChannelDisabled = 5;
|
||||
ModeDoesNotHandleTargetTemperatureMessage = 6;
|
||||
|
||||
UnknownError = 32;
|
||||
}
|
||||
@ -36,6 +41,31 @@ enum Mode {
|
||||
// AutoTune = 7;
|
||||
}
|
||||
|
||||
message ConstantTemperatureConfig
|
||||
{
|
||||
// What temperature source we need to talk to
|
||||
uint32 temperature_channel_id = 1;
|
||||
}
|
||||
|
||||
message TemperatureSlopeConfig
|
||||
{
|
||||
// What temperature source we need to talk to
|
||||
uint32 temperature_channel_id = 1;
|
||||
|
||||
// Desired temperature slope in °C per minute
|
||||
// defaults to 1°C per minute
|
||||
float slope = 2;
|
||||
}
|
||||
|
||||
message ModeConfig
|
||||
{
|
||||
oneof config
|
||||
{
|
||||
ConstantTemperatureConfig constant_temperature = 1;
|
||||
TemperatureSlopeConfig temperature_slope = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message ActiveChannelsRequest
|
||||
{
|
||||
}
|
||||
@ -84,17 +114,35 @@ message DeinitializeChannelResponse
|
||||
optional Error error = 254;
|
||||
}
|
||||
|
||||
// read or change the current mode in which the controller works
|
||||
message ModeRequest
|
||||
{
|
||||
// The ID of the operation channel
|
||||
uint32 channel_id = 1; // one of
|
||||
// The ID of the operation channel, this channel needs to be activated beforehand
|
||||
uint32 channel_id = 1;
|
||||
|
||||
optional Mode mode = 2;
|
||||
// optional configuration for selected mode, value ignored when mode is not set
|
||||
optional ModeConfig mode_config = 3;
|
||||
}
|
||||
|
||||
message ModeResponse
|
||||
{
|
||||
uint32 channel_id = 1;
|
||||
Mode mode = 2;
|
||||
ModeConfig mode_config = 3;
|
||||
optional Error error = 254;
|
||||
}
|
||||
|
||||
message TargetTemperatureRequest
|
||||
{
|
||||
uint32 channel_id = 1;
|
||||
optional float target_temperature = 2;
|
||||
}
|
||||
|
||||
message TargetTemperatureResponse
|
||||
{
|
||||
uint32 channel_id = 1;
|
||||
optional float target_temperature = 2;
|
||||
optional Error error = 254;
|
||||
}
|
||||
|
||||
@ -114,6 +162,7 @@ message IngressMessage
|
||||
DeinitializeChannelRequest deinitialize_channel_request = 3;
|
||||
|
||||
ModeRequest mode_request = 4;
|
||||
TargetTemperatureRequest target_temperature_request = 5;
|
||||
}
|
||||
};
|
||||
|
||||
@ -127,5 +176,6 @@ message EgressMessage
|
||||
DeinitializeChannelResponse deinitialize_channel_response = 3;
|
||||
|
||||
ModeResponse mode_response = 4;
|
||||
TargetTemperatureResponse target_temperature_response = 5;
|
||||
}
|
||||
};
|
||||
|
||||
@ -32,7 +32,7 @@ extern "C" unsigned int sleep(unsigned int seconds) {
|
||||
|
||||
using namespace rims;
|
||||
|
||||
static K_THREAD_STACK_DEFINE(k_messengerStack, 1500);
|
||||
static K_THREAD_STACK_DEFINE(k_messengerStack, 2000);
|
||||
TStack messengerStack{k_messengerStack, K_THREAD_STACK_SIZEOF(k_messengerStack)};
|
||||
|
||||
static K_THREAD_STACK_DEFINE(k_uartStack, 1300);
|
||||
|
||||
@ -1,9 +1,17 @@
|
||||
#include "operation.hpp"
|
||||
#include "inplace_vector.hpp"
|
||||
#include "log.hpp"
|
||||
#include "operation_pb_helpers.hpp"
|
||||
#include "messenger.hpp"
|
||||
#include "pid.hpp"
|
||||
#include "proto/ctrl.pb.h"
|
||||
#include "proto/operation.pb.h"
|
||||
#include "zephyr.hpp"
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
#include <variant>
|
||||
|
||||
namespace rims {
|
||||
@ -18,6 +26,29 @@ struct error : std::exception {
|
||||
}
|
||||
};
|
||||
|
||||
struct max_number_of_channels : error {
|
||||
max_number_of_channels() : error{op_Error_MaxNumberOfChannels} {
|
||||
}
|
||||
};
|
||||
|
||||
struct channel_used_multiple_times : error {
|
||||
channel_used_multiple_times() : error{op_Error_ControlChannelUsedMultipleTimes} {
|
||||
}
|
||||
};
|
||||
|
||||
struct channel_not_found : error {
|
||||
channel_not_found() : error{op_Error_ChannelNotFound} {
|
||||
}
|
||||
};
|
||||
|
||||
struct control_channel_disabled : error {
|
||||
control_channel_disabled() : error{op_Error_ControlChannelDisabled} {
|
||||
}
|
||||
};
|
||||
struct mode_does_not_accept_target_temperature : error {
|
||||
mode_does_not_accept_target_temperature() : error{op_Error_ModeDoesNotHandleTargetTemperatureMessage} {
|
||||
}
|
||||
};
|
||||
} // namespace op
|
||||
|
||||
K_FIFO_DEFINE(operationIngress);
|
||||
@ -28,20 +59,21 @@ fifo_queue<op_EgressMessage, 2> operationEgressQueue{operationEggress};
|
||||
|
||||
constexpr auto op_factory = handler_factory<op_IngressMessage, op_EgressMessage, op::error>{};
|
||||
|
||||
constexpr auto op_modeHandler = op_factory.make_handler<op_ModeRequest, &OperationOrchestrator::handler_modeRequest>();
|
||||
constexpr std::array<handler, 5> op_handlers = {
|
||||
op_factory.make_handler<op_ActiveChannelsRequest, &OperationOrchestrator::handle_activeChannelsRequest>(),
|
||||
op_factory.make_handler<op_InitializeChannelRequest, &OperationOrchestrator::handle_initializeChannelRequest>(),
|
||||
op_factory.make_handler<op_DeinitializeChannelRequest, &OperationOrchestrator::handle_deinitializeChannelRequest>(),
|
||||
op_factory.make_handler<op_ModeRequest, &OperationOrchestrator::handle_modeRequest>(),
|
||||
op_factory.make_handler<op_TargetTemperatureRequest, &OperationOrchestrator::handle_targetTemperatureRequest>(),
|
||||
};
|
||||
|
||||
OperationOrchestrator::OperationOrchestrator()
|
||||
: //
|
||||
_mode{DisabledMode{}, DisabledMode{}}, _pc{} {
|
||||
OperationOrchestrator::OperationOrchestrator() {
|
||||
ULOG_DEBUG("OperationOrchestrator start");
|
||||
|
||||
_tickTimer.start();
|
||||
|
||||
zephyr::event_pool::k_init(_events[0], _tickSem);
|
||||
operationIngressQueue.k_event_init(_events[1]);
|
||||
|
||||
/// TODO change
|
||||
setMode(0, op_Mode::op_Mode_ConstantTemperature);
|
||||
_tickTimer.start();
|
||||
}
|
||||
|
||||
void OperationOrchestrator::loop() {
|
||||
@ -60,10 +92,10 @@ void OperationOrchestrator::loop() {
|
||||
|
||||
void OperationOrchestrator::event_tick() {
|
||||
zephyr::semaphore::k_sem_take_now(_tickSem);
|
||||
|
||||
ULOG_INFO("PID update thread");
|
||||
|
||||
std::get<ConstantTemperatureMode>(_mode[0]).update();
|
||||
for (auto &channel : _channels) {
|
||||
channel.update();
|
||||
}
|
||||
}
|
||||
|
||||
void OperationOrchestrator::event_operationMessageArrived() {
|
||||
@ -71,16 +103,70 @@ void OperationOrchestrator::event_operationMessageArrived() {
|
||||
ULOG_INFO("operation request message handler");
|
||||
operationEgressQueue.try_produce([&](op_EgressMessage &resp) {
|
||||
ULOG_INFO("operation response message handler");
|
||||
op_modeHandler.execute(this, req, resp);
|
||||
for (const auto &handler : op_handlers) {
|
||||
if (handler.execute(this, req, resp)) break;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void OperationOrchestrator::handler_modeRequest(const ModeRequest &request, ModeResponse &resp) {
|
||||
void OperationOrchestrator::handle_activeChannelsRequest(const ActiveChannelsRequest &req, ActiveChannelsResponse &resp) const {
|
||||
ULOG_INFO("active channels request handler");
|
||||
resp.channels_count = _channels.size();
|
||||
for (auto i = 0; i < resp.channels_count; i++) {
|
||||
resp.channels[i] = _channels.at(i).id();
|
||||
}
|
||||
}
|
||||
|
||||
void OperationOrchestrator::handle_initializeChannelRequest(const InitializeChannelRequest &req, InitializeChannelResponse &resp) {
|
||||
ULOG_INFO("channel init request handler");
|
||||
|
||||
if (_channels.full()) throw op::max_number_of_channels{};
|
||||
|
||||
for (auto i = 0; i < req.ctrl_channels_count; i++) {
|
||||
const auto ch = req.ctrl_channels[i];
|
||||
for (const auto &opchannel : _channels) {
|
||||
if (opchannel.usesControlChannel(ch)) {
|
||||
throw op::channel_used_multiple_times{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beman::inplace_vector<uint8_t, sizeof(InitializeChannelRequest::ctrl_channels)> channels;
|
||||
for (auto i = 0; i < req.ctrl_channels_count; i++) {
|
||||
channels.push_back(req.ctrl_channels[i]);
|
||||
}
|
||||
|
||||
auto find_id = [&]() {
|
||||
for (uint8_t id = 0; id < 255; ++id) {
|
||||
bool used = std::any_of(_channels.begin(), _channels.end(), [id](const OperationChannel &ch) { return ch.id() == id; });
|
||||
if (!used) return id;
|
||||
}
|
||||
throw op::max_number_of_channels{};
|
||||
};
|
||||
|
||||
_channels.emplace_back(std::span{channels.begin(), channels.end()}, find_id());
|
||||
}
|
||||
|
||||
void OperationOrchestrator::handle_deinitializeChannelRequest(const DeinitializeChannelRequest &req, DeinitializeChannelResponse &resp) {
|
||||
auto found = std::find_if(_channels.begin(), _channels.end(), [&](const OperationChannel &ch) { return ch.id() == req.channel_id; });
|
||||
|
||||
if (found == _channels.end()) {
|
||||
ULOG_WARNING("channel %d not found", req.channel_id);
|
||||
throw op::channel_not_found{};
|
||||
}
|
||||
|
||||
resp.channel_id = found->id();
|
||||
resp.ctrl_channel_ids_count = found->copyControlChannels(resp.ctrl_channel_ids);
|
||||
|
||||
_channels.erase(found);
|
||||
}
|
||||
|
||||
void OperationOrchestrator::handle_modeRequest(const ModeRequest &request, ModeResponse &resp) {
|
||||
ULOG_INFO("mode request handler");
|
||||
/// TODO
|
||||
// checkChannel(request.channel_id);
|
||||
if (not channelExists(request.channel_id)) throw op::channel_not_found{};
|
||||
|
||||
if (request.has_mode) {
|
||||
setMode(request.channel_id, request.mode);
|
||||
}
|
||||
@ -88,60 +174,200 @@ void OperationOrchestrator::handler_modeRequest(const ModeRequest &request, Mode
|
||||
resp.mode = mode(request.channel_id);
|
||||
}
|
||||
|
||||
TemperatureSlopeMode::TemperatureSlopeMode(PowerControl &pc, PIDController &pid, const op_TemperatureSlopeConfig &config)
|
||||
: _powerControl{pc}, _pid{pid}, _tempChannel{static_cast<uint8_t>(config.temperature_channel_id)}, _slope{config.slope} {
|
||||
}
|
||||
|
||||
void TemperatureSlopeMode::update() {
|
||||
float currentTemperature = temp(_tempChannel);
|
||||
|
||||
// Use simple hysteresis to reduce mode-switch jitter
|
||||
constexpr float hysteresis = 0.25f;
|
||||
const bool closeToSetpoint = std::abs(targetTemperature() - currentTemperature) <= hysteresis;
|
||||
|
||||
auto useConstantTemperatureAlg = [&]() {
|
||||
if (_heating) {
|
||||
_pid.get().reset();
|
||||
_heating = false;
|
||||
}
|
||||
const float outputPower = _pid.get().update(targetTemperature(), currentTemperature);
|
||||
_powerControl.get().setPower(outputPower);
|
||||
};
|
||||
|
||||
auto useConstantSlope = [&]() {
|
||||
if (!_heating) {
|
||||
_pid.get().reset();
|
||||
_heating = true;
|
||||
}
|
||||
|
||||
const float dt = 1.0f; // 1s guaranteed by RTOS
|
||||
const float dT = currentTemperature - _lastTemperature;
|
||||
const float measuredSlope = (dT / dt) * 60.0f; // °C/min
|
||||
|
||||
const float outputPower = _pid.get().update(_slope, measuredSlope);
|
||||
_powerControl.get().setPower(outputPower);
|
||||
};
|
||||
|
||||
if (closeToSetpoint) {
|
||||
useConstantTemperatureAlg();
|
||||
} else {
|
||||
useConstantSlope();
|
||||
}
|
||||
|
||||
_lastTemperature = currentTemperature;
|
||||
}
|
||||
|
||||
ConstantTemperatureMode::ConstantTemperatureMode(PowerControl &pc, PIDController &pid, const op_ConstantTemperatureConfig &config)
|
||||
: _powerControl{pc}, _pid{pid}, _tempChannel{static_cast<uint8_t>(config.temperature_channel_id)} {
|
||||
|
||||
gpio_GpioRequest gpio = gpio_GpioRequest_init_zero;
|
||||
gpio.gpio = gpio_GPIO_GPIO_pin_2;
|
||||
gpio.has_state = false;
|
||||
auto resp = MessengerThread::ipc_request(gpio);
|
||||
|
||||
if (resp->state != true) {
|
||||
gpio.has_state = true;
|
||||
gpio.state = true;
|
||||
MessengerThread::ipc_request(gpio);
|
||||
ULOG_INFO("Pump turned on");
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds{1});
|
||||
}
|
||||
}
|
||||
|
||||
void ConstantTemperatureMode::update() {
|
||||
auto currentTemperature = temp(_tempChannel);
|
||||
float outputPower = _pid.get().update(targetTemperature(), currentTemperature);
|
||||
_powerControl.get().setPower(outputPower);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
concept HasSetTargetTemperature = requires(T t, float temp) {
|
||||
{ t.setTargetTemperature(temp) } -> std::same_as<void>;
|
||||
};
|
||||
template <typename T>
|
||||
concept HasTargetTemperature = requires(T t) {
|
||||
{ t.targetTemperature() } -> std::convertible_to<float>;
|
||||
};
|
||||
|
||||
void OperationOrchestrator::handle_targetTemperatureRequest(const TargetTemperatureRequest &req, TargetTemperatureResponse &resp) {
|
||||
ULOG_INFO("target temperature request handler");
|
||||
if (not channelExists(req.channel_id)) throw op::channel_not_found{};
|
||||
find(req.channel_id).handle_targetTemperatureRequest(req, resp);
|
||||
}
|
||||
|
||||
void OperationChannel::handle_targetTemperatureRequest(const TargetTemperatureRequest &req, TargetTemperatureResponse &resp) {
|
||||
ULOG_INFO("target temperature request handler");
|
||||
if (req.has_target_temperature) {
|
||||
std::visit(
|
||||
[&](auto &obj) {
|
||||
if constexpr (HasSetTargetTemperature<decltype(obj)>) {
|
||||
obj.setTargetTemperature(req.target_temperature);
|
||||
} else {
|
||||
throw op::mode_does_not_accept_target_temperature{};
|
||||
}
|
||||
},
|
||||
_mode
|
||||
);
|
||||
}
|
||||
|
||||
resp.has_target_temperature = true;
|
||||
resp.target_temperature = std::visit(
|
||||
[&](auto &obj) -> float {
|
||||
if constexpr (HasTargetTemperature<decltype(obj)>) {
|
||||
return obj.targetTemperature();
|
||||
} else {
|
||||
throw op::mode_does_not_accept_target_temperature{};
|
||||
}
|
||||
},
|
||||
_mode
|
||||
);
|
||||
}
|
||||
|
||||
void OperationOrchestrator::setMode(uint8_t ch, op_Mode mode) {
|
||||
ULOG_INFO("channel %d mode change to %d", ch, mode);
|
||||
/// TODO check channel
|
||||
if (not channelExists(ch)) throw op::channel_not_found{};
|
||||
}
|
||||
|
||||
void OperationChannel::setMode(Mode mode, const std::optional<op_ModeConfig> &config) {
|
||||
switch (mode) {
|
||||
case op_Mode_Disabled:
|
||||
_mode.at(ch).emplace<DisabledMode>();
|
||||
case op_Mode_Disabled: {
|
||||
_mode.emplace<DisabledMode>();
|
||||
break;
|
||||
case op_Mode_ConstantTemperature:
|
||||
_mode.at(ch).emplace<ConstantTemperatureMode>(_pc, _pid);
|
||||
}
|
||||
case op_Mode_ConstantTemperature: {
|
||||
constexpr auto defaultConfig = op_ModeConfig{
|
||||
.which_config = op_ModeConfig_constant_temperature_tag, //
|
||||
.config = {.constant_temperature = {op_ConstantTemperatureConfig{.temperature_channel_id = 0}}}
|
||||
};
|
||||
|
||||
_mode.emplace<ConstantTemperatureMode>(_pc, _pid, config.value_or(defaultConfig).config.constant_temperature);
|
||||
break;
|
||||
case op_Mode_TemperatureSlope:
|
||||
_mode.at(ch).emplace<TemperatureSlopeMode>();
|
||||
}
|
||||
case op_Mode_TemperatureSlope: {
|
||||
constexpr auto defaultConfig = op_ModeConfig{
|
||||
.which_config = op_ModeConfig_temperature_slope_tag, //
|
||||
.config = {.temperature_slope = {op_TemperatureSlopeConfig{.temperature_channel_id = 0, .slope = 1.0f}}}
|
||||
};
|
||||
|
||||
_mode.emplace<TemperatureSlopeMode>(_pc, _pid, config.value_or(defaultConfig).config.temperature_slope);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
op_Mode OperationOrchestrator::mode(uint8_t ch) const noexcept {
|
||||
/// TODO check channel
|
||||
if (std::holds_alternative<ConstantTemperatureMode>(_mode.at(ch))) {
|
||||
if (not channelExists(ch)) return op_Mode_Disabled;
|
||||
else return find(ch).mode();
|
||||
}
|
||||
|
||||
Mode OperationChannel::mode() const noexcept {
|
||||
if (std::holds_alternative<ConstantTemperatureMode>(_mode)) {
|
||||
return op_Mode_ConstantTemperature;
|
||||
}
|
||||
if (std::holds_alternative<TemperatureSlopeMode>(_mode.at(ch))) {
|
||||
if (std::holds_alternative<TemperatureSlopeMode>(_mode)) {
|
||||
return op_Mode_TemperatureSlope;
|
||||
}
|
||||
|
||||
return op_Mode_Disabled;
|
||||
}
|
||||
|
||||
bool OperationOrchestrator::channelExists(uint8_t ch) const {
|
||||
auto found = std::find_if(_channels.begin(), _channels.end(), [&](const OperationChannel &opch) { return opch.id() == ch; });
|
||||
return found != _channels.end();
|
||||
}
|
||||
|
||||
void OperationThread::threadMain() {
|
||||
ULOG_DEBUG("OperationThread start");
|
||||
OperationOrchestrator thread{};
|
||||
thread.loop();
|
||||
}
|
||||
|
||||
PowerControl::PowerControl() {
|
||||
PowerControl::PowerControl(std::span<uint8_t> channels) : _control_channels{channels.begin(), channels.end()} {
|
||||
ULOG_DEBUG("PowerControl start");
|
||||
|
||||
ctrl_PowerControlAlgorithmRequest plar = ctrl_PowerControlAlgorithmRequest_init_zero;
|
||||
plar.alg = ctrl_PowerControlAlgorithm::ctrl_PowerControlAlgorithm_GroupModulation;
|
||||
plar.has_alg = true;
|
||||
plar.channel_id = 0;
|
||||
MessengerThread::ipc_request(plar);
|
||||
for (const auto ch : _control_channels) {
|
||||
ctrl_PowerControlAlgorithmRequest plar = ctrl_PowerControlAlgorithmRequest_init_zero;
|
||||
plar.channel_id = ch;
|
||||
auto resp = MessengerThread::ipc_request(plar);
|
||||
|
||||
plar.channel_id = 1;
|
||||
MessengerThread::ipc_request(plar);
|
||||
if (not resp.has_value()) {
|
||||
ULOG_ERROR("Something went really bad ;( during PowerControlAlgorithm check");
|
||||
throw op::error{};
|
||||
}
|
||||
|
||||
gpio_GpioRequest gpio = gpio_GpioRequest_init_zero;
|
||||
gpio.gpio = gpio_GPIO_GPIO_pin_2;
|
||||
gpio.has_state = true;
|
||||
gpio.state = false;
|
||||
MessengerThread::ipc_request(gpio);
|
||||
ULOG_INFO("GPIO SET");
|
||||
if (resp->has_error) {
|
||||
ULOG_WARNING("ctrl returned error:%d", resp->error);
|
||||
throw op::error{};
|
||||
}
|
||||
|
||||
if (resp->alg == ctrl_PowerControlAlgorithm_NoModulation) {
|
||||
ULOG_WARNING("ctrl channel %d disabled", resp->channel_id);
|
||||
throw op::control_channel_disabled{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PowerControl::setPower(float percent) {
|
||||
|
||||
@ -4,13 +4,19 @@
|
||||
#include "common.hpp"
|
||||
#include "fifo_queue.hpp"
|
||||
|
||||
#include "inplace_vector.hpp"
|
||||
#include "log.hpp"
|
||||
#include "messenger.hpp"
|
||||
#include "operation_pb_helpers.hpp"
|
||||
#include "pid.hpp"
|
||||
#include "proto/operation.pb.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <span>
|
||||
#include <variant>
|
||||
|
||||
namespace rims {
|
||||
@ -21,77 +27,185 @@ namespace rims {
|
||||
extern fifo_queue<op_IngressMessage, 2> operationIngressQueue;
|
||||
extern fifo_queue<op_EgressMessage, 2> operationEgressQueue;
|
||||
|
||||
using ModeRequest = op_ModeRequest;
|
||||
using ModeResponse = op_ModeResponse;
|
||||
using Mode = op_Mode;
|
||||
|
||||
using ActiveChannelsRequest = op_ActiveChannelsRequest;
|
||||
using ActiveChannelsResponse = op_ActiveChannelsResponse;
|
||||
using InitializeChannelRequest = op_InitializeChannelRequest;
|
||||
using InitializeChannelResponse = op_InitializeChannelResponse;
|
||||
using DeinitializeChannelRequest = op_DeinitializeChannelRequest;
|
||||
using DeinitializeChannelResponse = op_DeinitializeChannelResponse;
|
||||
|
||||
using ModeRequest = op_ModeRequest;
|
||||
using ModeResponse = op_ModeResponse;
|
||||
using TargetTemperatureRequest = op_TargetTemperatureRequest;
|
||||
using TargetTemperatureResponse = op_TargetTemperatureResponse;
|
||||
|
||||
class ModeStrategy {
|
||||
public:
|
||||
ModeStrategy() {
|
||||
}
|
||||
|
||||
void setTargetTemperature(float temp) {
|
||||
_setpoint = temp;
|
||||
}
|
||||
float targetTemperature() const {
|
||||
return _setpoint;
|
||||
}
|
||||
float temp(uint8_t ch) const {
|
||||
temperature_GetTemperatureRequest req = {};
|
||||
req.channel_id = ch;
|
||||
|
||||
auto response = MessengerThread::ipc_request(req);
|
||||
|
||||
if (response.has_value()) {
|
||||
if (response.has_value() and not response->has_error) {
|
||||
return response.value().temperature_current.temp_c;
|
||||
}
|
||||
ULOG_WARNING("GotTemp ch:%d, error", ch);
|
||||
return 0; /// TODO better error handling?
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual ~ModeStrategy() {
|
||||
virtual ~ModeStrategy(){
|
||||
/// todo set channel power to 0 at exit
|
||||
};
|
||||
|
||||
private:
|
||||
float _setpoint{30};
|
||||
};
|
||||
|
||||
class PowerControl {
|
||||
public:
|
||||
PowerControl();
|
||||
PowerControl(std::span<uint8_t> channels);
|
||||
|
||||
void setPower(float percent);
|
||||
};
|
||||
|
||||
class DisabledMode : public ModeStrategy {};
|
||||
|
||||
class ConstantTemperatureMode : public ModeStrategy {
|
||||
public:
|
||||
ConstantTemperatureMode(PowerControl &pc, PIDController &pid) : _powerControl{pc}, _pid{pid} {
|
||||
/// TODO check power control algorithm, should not be disabled
|
||||
/// TODO check pump GPIO state, should be enabled
|
||||
bool usesChannel(uint8_t ch) const {
|
||||
return std::find(_control_channels.begin(), _control_channels.end(), ch) != _control_channels.end();
|
||||
}
|
||||
|
||||
void update() {
|
||||
auto currentTemperature = temp(0);
|
||||
float outputPower = _pid.update(_setpoint, currentTemperature);
|
||||
_powerControl.setPower(outputPower);
|
||||
int copyControlChannels(uint32_t *ch) const {
|
||||
for (std::size_t i = 0; i < _control_channels.size(); i++) {
|
||||
ch[i] = _control_channels.at(i);
|
||||
}
|
||||
return _control_channels.size();
|
||||
}
|
||||
|
||||
private:
|
||||
float _setpoint{30};
|
||||
PowerControl &_powerControl;
|
||||
|
||||
PIDController &_pid;
|
||||
beman::inplace_vector<uint8_t, 16> _control_channels;
|
||||
};
|
||||
|
||||
class TemperatureSlopeMode : public ModeStrategy {};
|
||||
class DisabledMode : public ModeStrategy {
|
||||
public:
|
||||
void update() {
|
||||
}
|
||||
};
|
||||
|
||||
class ConstantTemperatureMode : public ModeStrategy {
|
||||
public:
|
||||
ConstantTemperatureMode(PowerControl &pc, PIDController &pid, const op_ConstantTemperatureConfig &config);
|
||||
// Disallow copy
|
||||
ConstantTemperatureMode(const ConstantTemperatureMode &) = delete;
|
||||
ConstantTemperatureMode &operator=(const ConstantTemperatureMode &) = delete;
|
||||
|
||||
// Allow move
|
||||
ConstantTemperatureMode(ConstantTemperatureMode &&) = default;
|
||||
ConstantTemperatureMode &operator=(ConstantTemperatureMode &&) = default;
|
||||
|
||||
void update();
|
||||
|
||||
|
||||
private:
|
||||
/// reference wrapper allows this class to be movable
|
||||
std::reference_wrapper<PowerControl> _powerControl;
|
||||
std::reference_wrapper<PIDController> _pid;
|
||||
uint8_t _tempChannel;
|
||||
};
|
||||
|
||||
class TemperatureSlopeMode : public ModeStrategy {
|
||||
public:
|
||||
TemperatureSlopeMode(PowerControl &pc, PIDController &pid, const op_TemperatureSlopeConfig &config);
|
||||
// Disallow copy
|
||||
TemperatureSlopeMode(const TemperatureSlopeMode &) = delete;
|
||||
TemperatureSlopeMode &operator=(const TemperatureSlopeMode &) = delete;
|
||||
|
||||
// Allow move
|
||||
TemperatureSlopeMode(TemperatureSlopeMode &&) = default;
|
||||
TemperatureSlopeMode &operator=(TemperatureSlopeMode &&) = default;
|
||||
|
||||
void update();
|
||||
|
||||
private:
|
||||
/// reference wrapper allows this class to be movable
|
||||
float _lastTemperature;
|
||||
std::reference_wrapper<PowerControl> _powerControl;
|
||||
std::reference_wrapper<PIDController> _pid;
|
||||
uint8_t _tempChannel{0};
|
||||
float _slope{1.0f};
|
||||
bool _heating{false};
|
||||
};
|
||||
|
||||
/*
|
||||
* global operation of the device, it listens to temperature changes, and listens to user requests
|
||||
* This class represents a 'virtual' channel,
|
||||
*/
|
||||
class OperationOrchestrator {
|
||||
using Mode = std::variant< //
|
||||
class OperationChannel {
|
||||
using WorkMode = std::variant< //
|
||||
DisabledMode,
|
||||
ConstantTemperatureMode,
|
||||
TemperatureSlopeMode>;
|
||||
|
||||
public:
|
||||
void setMode(Mode mode, const std::optional<op_ModeConfig> &config);
|
||||
Mode mode() const noexcept;
|
||||
|
||||
public:
|
||||
OperationChannel(std::span<uint8_t> control_channels, uint8_t id) : _pc{control_channels}, _ownId{id} {
|
||||
}
|
||||
// Disallow copy
|
||||
OperationChannel(const OperationChannel &) = delete;
|
||||
OperationChannel &operator=(const OperationChannel &) = delete;
|
||||
|
||||
// Allow move
|
||||
OperationChannel(OperationChannel &&) = default;
|
||||
OperationChannel &operator=(OperationChannel &&) = default;
|
||||
|
||||
bool usesControlChannel(uint8_t ch) const {
|
||||
return _pc.usesChannel(ch);
|
||||
}
|
||||
int copyControlChannels(uint32_t *ch) const {
|
||||
return _pc.copyControlChannels(ch);
|
||||
}
|
||||
|
||||
uint8_t id() const {
|
||||
return _ownId;
|
||||
}
|
||||
|
||||
void update() {
|
||||
std::visit([](auto &mode) { mode.update(); }, _mode);
|
||||
}
|
||||
|
||||
void handle_targetTemperatureRequest(const TargetTemperatureRequest &request, TargetTemperatureResponse &resp);
|
||||
|
||||
private:
|
||||
WorkMode _mode;
|
||||
PIDController _pid{2.0f, 0.5f, 0.1f, 1.0f};
|
||||
PowerControl _pc;
|
||||
uint8_t _ownId;
|
||||
};
|
||||
|
||||
/*
|
||||
* global operation of the device, it listens to temperature changes, and listens to user requests
|
||||
*/
|
||||
class OperationOrchestrator {
|
||||
|
||||
public:
|
||||
OperationOrchestrator();
|
||||
void loop();
|
||||
|
||||
void handler_modeRequest(const ModeRequest &request, ModeResponse &resp);
|
||||
void handle_activeChannelsRequest(const ActiveChannelsRequest &req, ActiveChannelsResponse &resp) const;
|
||||
void handle_initializeChannelRequest(const InitializeChannelRequest &req, InitializeChannelResponse &resp);
|
||||
void handle_deinitializeChannelRequest(const DeinitializeChannelRequest &req, DeinitializeChannelResponse &resp);
|
||||
|
||||
void handle_modeRequest(const ModeRequest &request, ModeResponse &resp);
|
||||
void handle_targetTemperatureRequest(const TargetTemperatureRequest &request, TargetTemperatureResponse &resp);
|
||||
|
||||
private:
|
||||
void event_tick();
|
||||
@ -100,20 +214,28 @@ class OperationOrchestrator {
|
||||
void setMode(uint8_t ch, op_Mode mode);
|
||||
op_Mode mode(uint8_t ch) const noexcept;
|
||||
|
||||
std::array<Mode, 2> _mode; /// TODO set maximum number of channels to max channels
|
||||
bool channelExists(uint8_t ch) const;
|
||||
|
||||
OperationChannel &find(uint8_t ch) {
|
||||
auto found = std::find_if(_channels.begin(), _channels.end(), [&](const OperationChannel &opch) { return opch.id() == ch; });
|
||||
return *found;
|
||||
}
|
||||
const OperationChannel &find(uint8_t ch) const {
|
||||
auto found = std::find_if(_channels.begin(), _channels.end(), [&](const OperationChannel &opch) { return opch.id() == ch; });
|
||||
return *found;
|
||||
}
|
||||
|
||||
std::array<k_poll_event, 2> _events;
|
||||
|
||||
zephyr::semaphore::sem _tickSem{0, 1};
|
||||
RecurringSemaphoreTimer _tickTimer{_tickSem, std::chrono::seconds{1}};
|
||||
|
||||
/// TODO This should go to a separate class, one for each controlled channel
|
||||
PIDController _pid{2.0f, 0.5f, 0.1f, 1.0f};
|
||||
PowerControl _pc;
|
||||
beman::inplace_vector<OperationChannel, 2> _channels; /// only 2 channels supported ATM
|
||||
};
|
||||
|
||||
class OperationThread : public ZephyrThread {
|
||||
public:
|
||||
OperationThread(TStackBase &stack) : ZephyrThread(stack, 5, 0, "Operation") {};
|
||||
OperationThread(TStackBase &stack) : ZephyrThread(stack, 5, 0, "Operation"){};
|
||||
|
||||
void threadMain() override;
|
||||
};
|
||||
|
||||
@ -6,61 +6,71 @@
|
||||
|
||||
namespace rims {
|
||||
|
||||
template <> constexpr int tag<op_ModeRequest>() {
|
||||
return op_IngressMessage_mode_request_tag;
|
||||
template <> constexpr int tag<op_ActiveChannelsRequest>() {
|
||||
return op_IngressMessage_active_channels_request_tag;
|
||||
}
|
||||
template <> constexpr int tag<op_InitializeChannelRequest>() {
|
||||
return op_IngressMessage_initialize_channel_request_tag;
|
||||
}
|
||||
template <> constexpr int tag<op_ActiveChannelsRequest>() {
|
||||
return op_IngressMessage_active_channels_request_tag;
|
||||
}
|
||||
template <> constexpr int tag<op_DeinitializeChannelRequest>() {
|
||||
return op_IngressMessage_deinitialize_channel_request_tag;
|
||||
}
|
||||
template <> constexpr int tag<op_ModeRequest>() {
|
||||
return op_IngressMessage_mode_request_tag;
|
||||
}
|
||||
template <> constexpr int tag<op_TargetTemperatureRequest>() {
|
||||
return op_IngressMessage_target_temperature_request_tag;
|
||||
}
|
||||
|
||||
template <> constexpr int tag<op_ModeResponse>() {
|
||||
return op_EgressMessage_mode_response_tag;
|
||||
template <> constexpr int tag<op_ActiveChannelsResponse>() {
|
||||
return op_EgressMessage_active_channels_response_tag;
|
||||
}
|
||||
template <> constexpr int tag<op_InitializeChannelResponse>() {
|
||||
return op_EgressMessage_initialize_channel_response_tag;
|
||||
}
|
||||
template <> constexpr int tag<op_ActiveChannelsResponse>() {
|
||||
return op_EgressMessage_active_channels_response_tag;
|
||||
}
|
||||
template <> constexpr int tag<op_DeinitializeChannelResponse>() {
|
||||
return op_EgressMessage_deinitialize_channel_response_tag;
|
||||
}
|
||||
template <> constexpr int tag<op_ModeResponse>() {
|
||||
return op_EgressMessage_mode_response_tag;
|
||||
}
|
||||
template <> constexpr int tag<op_TargetTemperatureResponse>() {
|
||||
return op_EgressMessage_target_temperature_response_tag;
|
||||
}
|
||||
|
||||
// Ingress and egress message categorization for operation package
|
||||
template <typename MsgT>
|
||||
constexpr bool is_op_ingress_msg_v = is_any_of_v<
|
||||
MsgT, //
|
||||
op_ModeRequest,
|
||||
op_InitializeChannelRequest,
|
||||
op_ActiveChannelsRequest,
|
||||
op_DeinitializeChannelRequest>;
|
||||
op_InitializeChannelRequest,
|
||||
op_DeinitializeChannelRequest,
|
||||
op_ModeRequest,
|
||||
op_TargetTemperatureRequest>;
|
||||
|
||||
template <typename MsgT>
|
||||
constexpr bool is_op_egress_msg_v = is_any_of_v<
|
||||
MsgT, //
|
||||
op_ModeResponse,
|
||||
op_InitializeChannelResponse,
|
||||
op_ActiveChannelsResponse,
|
||||
op_DeinitializeChannelResponse>;
|
||||
op_InitializeChannelResponse,
|
||||
op_DeinitializeChannelResponse,
|
||||
op_ModeResponse,
|
||||
op_TargetTemperatureResponse>;
|
||||
|
||||
// ResponseSelector specializations
|
||||
template <> struct ResponseSelector<op_ModeRequest> {
|
||||
using response_t = op_ModeResponse;
|
||||
template <> struct ResponseSelector<op_ActiveChannelsRequest> {
|
||||
using response_t = op_ActiveChannelsResponse;
|
||||
};
|
||||
template <> struct ResponseSelector<op_InitializeChannelRequest> {
|
||||
using response_t = op_InitializeChannelResponse;
|
||||
};
|
||||
template <> struct ResponseSelector<op_ActiveChannelsRequest> {
|
||||
using response_t = op_ActiveChannelsResponse;
|
||||
};
|
||||
template <> struct ResponseSelector<op_DeinitializeChannelRequest> {
|
||||
using response_t = op_DeinitializeChannelResponse;
|
||||
};
|
||||
|
||||
template <> struct ResponseSelector<op_ModeRequest> {
|
||||
using response_t = op_ModeResponse;
|
||||
};
|
||||
template <> struct ResponseSelector<op_TargetTemperatureRequest> {
|
||||
using response_t = op_TargetTemperatureResponse;
|
||||
};
|
||||
} // namespace rims
|
||||
|
||||
@ -52,7 +52,7 @@ fifo_queue<temperature_EgressMessage, 2> temperatureEgressQueue{temperatureEggr
|
||||
|
||||
constexpr auto temperature_factory = handler_factory<temperature_IngressMessage, temperature_EgressMessage, temp::error>{};
|
||||
|
||||
constexpr std::array<handler, 5> temp_modeHandlers = {
|
||||
constexpr std::array<handler, 5> temp_handlers = {
|
||||
temperature_factory.make_handler<GetTemperatureRequest, &TemperatureSamplerOrchestrator::handle_getTemperatureRequest>(),
|
||||
temperature_factory.make_handler<GetActiveChannelsRequest, &TemperatureSamplerOrchestrator::handle_activeChannelsRequest>(),
|
||||
temperature_factory.make_handler<SamplerConfigRequest, &TemperatureSamplerOrchestrator::handle_samplerConfigRequest>(),
|
||||
@ -110,7 +110,7 @@ TemperatureSampler::TemperatureSampler(uint8_t channel) : _temperature{0.05}, _c
|
||||
|
||||
void TemperatureSampler::calibration() {
|
||||
/// ~1ms
|
||||
constexpr auto stabilizationTime = std::chrono::microseconds{2000};
|
||||
constexpr auto stabilizationTime = std::chrono::microseconds{1000};
|
||||
|
||||
ULOG_TRACE("cas ch:%d start", _channel);
|
||||
select_rmin();
|
||||
@ -339,7 +339,7 @@ void TemperatureSamplerOrchestrator::event_messageArrived() {
|
||||
ULOG_DEBUG("Producing temperature_EgressMessage response");
|
||||
auto ok = temperatureIngressQueue.try_consume([&](temperature_IngressMessage &req) {
|
||||
ULOG_DEBUG("Consuming temperature_IngressMessage request");
|
||||
for (const auto &handler : temp_modeHandlers) {
|
||||
for (const auto &handler : temp_handlers) {
|
||||
if (handler.execute(this, req, resp)) break;
|
||||
}
|
||||
});
|
||||
|
||||
@ -19,8 +19,8 @@ class uart_rx_buffer_overflow;
|
||||
class uart_rx_not_ready_error;
|
||||
|
||||
class AsyncUART {
|
||||
using rx_buffer_t = beman::inplace_vector<uint8_t, 256>;
|
||||
using tx_buffer_t = RingBuffer<uint8_t, 256>;
|
||||
using rx_buffer_t = beman::inplace_vector<uint8_t, 250>;
|
||||
using tx_buffer_t = RingBuffer<uint8_t, 350>;
|
||||
|
||||
public:
|
||||
AsyncUART();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user