add more checks

This commit is contained in:
Bartosz Wieczorek 2025-07-25 11:08:58 +02:00
parent 778e28aafa
commit 4acafea1b0
6 changed files with 133 additions and 61 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 17.0.0, 2025-06-20T07:57:23. -->
<!-- Written by QtCreator 17.0.0, 2025-07-25T08:23:37. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
@ -109,15 +109,15 @@
<value type="int" key="CMake.Configure.BaseEnvironment">2</value>
<value type="bool" key="CMake.Configure.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="CMake.Configure.UserEnvironmentChanges"/>
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_GENERATOR:STRING=Ninja
<value type="QString" key="CMake.Initial.Parameters">-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
-DQT_QMAKE_EXECUTABLE:FILEPATH=%{Qt:qmakeExecutable}
-DCMAKE_BUILD_TYPE:STRING=Build
-DQT_MAINTENANCE_TOOL:FILEPATH=/opt/Qt/MaintenanceTool
-DCMAKE_CXX_COMPILER:FILEPATH=%{Compiler:Executable:Cxx}
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
-DCMAKE_PREFIX_PATH:PATH=%{Qt:QT_INSTALL_PREFIX}
-DCMAKE_PROJECT_INCLUDE_BEFORE:FILEPATH=%{BuildConfig:BuildDirectory:NativeFilePath}/.qtc/package-manager/auto-setup.cmake
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}
-DQT_MAINTENANCE_TOOL:FILEPATH=/opt/Qt/MaintenanceTool</value>
-DCMAKE_COLOR_DIAGNOSTICS:BOOL=ON
-DCMAKE_BUILD_TYPE:STRING=Build
-DCMAKE_GENERATOR:STRING=Ninja
-DCMAKE_C_COMPILER:FILEPATH=%{Compiler:Executable:C}</value>
<value type="QString" key="CMake.Source.Directory">/home/bartoszek/zephyrproject/zephyr/rims_app</value>
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/home/bartoszek/zephyrproject/zephyr/rims_app/build</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
@ -221,14 +221,36 @@
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">zephyr_final</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.CMakeRunConfiguration.</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">zephyr_final</value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/bartoszek/zephyrproject/zephyr/rims_app/build/zephyr</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.1">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">zephyr_pre0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.CMakeRunConfiguration.</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">zephyr_pre0</value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/bartoszek/zephyrproject/zephyr/rims_app/build/zephyr</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">2</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
@ -289,14 +311,36 @@
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">zephyr_final</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.CMakeRunConfiguration.</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">zephyr_final</value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/bartoszek/zephyrproject/zephyr/rims_app/build/zephyr</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.1">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Valgrind.Callgrind.CostFormat">0</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<value type="QList&lt;int&gt;" key="Analyzer.Valgrind.VisibleErrorKinds"></value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">zephyr_pre0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">CMakeProjectManager.CMakeRunConfiguration.</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">zephyr_pre0</value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
<value type="QString" key="RunConfiguration.WorkingDirectory.default">/home/bartoszek/zephyrproject/zephyr/rims_app/build/zephyr</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">2</value>
</valuemap>
</data>
<data>

View File

@ -26,7 +26,7 @@ struct CommunicationCallback {
/*
* Sends bytes back. Bytes represents top level Egress message from each
*/
virtual std::expected<uint32_t, Error> send(std::span<std::byte> submessage) const = 0;
NDIS virtual std::expected<uint32_t, Error> send(std::span<std::byte> submessage) const = 0;
};
struct UartCallback : public CommunicationCallback {
@ -36,7 +36,7 @@ struct UartCallback : public CommunicationCallback {
const pb_msgdesc_t *_fields; // fields of egress message
WireFormatID _sender;
std::expected<uint32_t, Error> send(std::span<std::byte> subsystemMessage) const override;
NDIS std::expected<uint32_t, Error> send(std::span<std::byte> subsystemMessage) const override;
};
struct IPCCallback : public CommunicationCallback {
@ -45,14 +45,14 @@ struct IPCCallback : public CommunicationCallback {
function<void(std::span<std::byte> subsystemMessage)> _copy;
std::reference_wrapper<zephyr::semaphore::sem> _sem;
std::expected<uint32_t, Error> send(std::span<std::byte> subsystemMessage) const override;
NDIS std::expected<uint32_t, Error> send(std::span<std::byte> subsystemMessage) const override;
};
struct NoCallback : public CommunicationCallback {
// CommunicationCallback interface
public:
std::expected<uint32_t, Error> send(std::span<std::byte> submessage) const override {
NDIS std::expected<uint32_t, Error> send(std::span<std::byte> submessage) const override {
return {};
}
};
@ -147,7 +147,7 @@ class MessageDispatcher {
}
};
std::array<RequestData, 8> _activeRequests;
std::array<RequestData, 8> _activeRequests;
zephyr::mutex::ZepryrSpinlock _lock;
};
@ -195,6 +195,5 @@ class MessengerThread : public ZephyrThread {
std::array<k_poll_event, 8> _events;
public:
};
} // namespace rims

View File

@ -1,12 +1,12 @@
#include "operation.hpp"
#include "common.hpp"
#include "ctrl.hpp"
#include "error.hpp"
#include "inplace_vector.hpp"
#include "log.hpp"
#include "messenger.hpp"
#include "pid.hpp"
#include "zephyr.hpp"
#include "ctrl.hpp"
#include <algorithm>
#include <chrono>
@ -73,26 +73,28 @@ void OperationOrchestrator::event_tick() {
zephyr::semaphore::k_sem_take_now(_tickSem);
ULOG_INFO("PID update thread");
for (auto &channel : _channels) {
channel.update();
if (auto r = channel.update(); not r){
ULOG_WARNING("Operation Orchestrator failed to 'make a tick' for channel %d with err %d/%d", channel.id(), r.error().endpoint, r.error().code);
}
}
}
void OperationOrchestrator::event_operationMessageArrived() {
static op_IngressMessage req;
static op_EgressMessage resp;
constexpr auto service = "operation";
constexpr auto service = "operation";
ULOG_INFO("%s consume ingress", service);
operationIngressQueue.try_consume([&](const auto &_req) {
req = _req;
return true;
});
ULOG_INFO("%s execute handler", service);
for (const auto &handler : op_handlers) {
if (handler.execute(this, req, resp)) break;
}
ULOG_INFO("%s produce egress", service);
operationEgressQueue.try_produce(resp);
}
@ -116,6 +118,7 @@ std::expected<void, op::error> OperationOrchestrator::handle_initializeChannelRe
const auto ch = req.ctrl_channels[i];
for (const auto &opchannel : _channels) {
if (opchannel.usesControlChannel(ch)) {
ULOG_WARNING("opchannel %d alreadu uses channel %d", opchannel.id(), ch);
return std::unexpected{op::channel_used_multiple_times{}};
}
}
@ -131,13 +134,16 @@ std::expected<void, op::error> OperationOrchestrator::handle_initializeChannelRe
bool used = std::any_of(_channels.begin(), _channels.end(), [id](const OperationChannel &ch) { return ch.id() == id; });
if (!used) return id;
}
ULOG_WARNING("There are no more valid channel IDs");
return std::unexpected{op::max_number_of_channels{}};
};
// create OperationChannel for channels and id
auto activatedChannels = TRY(OperationChannel::check_channels(std::span{channels}));
auto id = TRY(find_id());
const auto &newChannel = _channels.emplace_back(activatedChannels, id);
auto activatedChannels = TRY(OperationChannel::check_channels(std::span{channels}));
ULOG_DEBUG("all ctrl channels seams to be active");
auto id = TRY(find_id());
const auto &newChannel = _channels.emplace_back(activatedChannels, id);
ULOG_INFO("created channel %d", newChannel.id());
resp.has_data = true;
resp.data.channel = newChannel.id();
@ -169,10 +175,11 @@ std::expected<void, op::error> OperationOrchestrator::handle_modeRequest(const M
return {};
}
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} {
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() {
std::expected<void, op::error> TemperatureSlopeMode::update() {
float currentTemperature = temp(_tempChannel).temperature_c;
// Use simple hysteresis to reduce mode-switch jitter
@ -185,7 +192,7 @@ void TemperatureSlopeMode::update() {
_heating = false;
}
const float outputPower = _pid.get().update(targetTemperature().temperature_c, currentTemperature);
_powerControl.get().setPower(outputPower);
return _powerControl.get().setPower(outputPower);
};
auto useConstantSlope = [&]() {
@ -199,13 +206,13 @@ void TemperatureSlopeMode::update() {
const float measuredSlope = (dT / dt) * 60.0f; // °C/min
const float outputPower = _pid.get().update(_slope, measuredSlope);
_powerControl.get().setPower(outputPower);
return _powerControl.get().setPower(outputPower);
};
if (closeToSetpoint) {
useConstantTemperatureAlg();
return useConstantTemperatureAlg();
} else {
useConstantSlope();
return useConstantSlope();
}
_lastTemperature = currentTemperature;
@ -227,10 +234,11 @@ ConstantTemperatureMode::ConstantTemperatureMode(PowerControl &pc, PIDController
}
}
void ConstantTemperatureMode::update() {
std::expected<void, op::error> ConstantTemperatureMode::update() {
auto currentTemperature = temp(_tempChannel).temperature_c;
float outputPower = _pid.get().update(targetTemperature().temperature_c, currentTemperature);
_powerControl.get().setPower(outputPower);
CHECK(_powerControl.get().setPower(outputPower));
return {};
}
std::expected<void, op::error> OperationOrchestrator::handle_targetTemperatureRequest(const TargetTemperatureRequest &req, TargetTemperatureResponse &resp) {
@ -349,7 +357,7 @@ PowerControl::PowerControl(std::span<uint8_t> channels) : _control_channels{chan
ULOG_DEBUG("PowerControl start");
}
void PowerControl::setPower(float percent) {
std::expected<void, op::error> PowerControl::setPower(float percent) {
ULOG_INFO("Set power %f", (double)percent);
for (const auto ch : _control_channels) {
@ -360,6 +368,8 @@ void PowerControl::setPower(float percent) {
MessengerThread::ipc_request(plr);
}
return {};
}
} // namespace rims

View File

@ -87,7 +87,7 @@ class PowerControl {
static std::expected<void, op::error> check_channels(std::span<uint8_t> channels);
PowerControl(std::span<uint8_t> channels);
void setPower(float percent);
NDIS std::expected<void, op::error> setPower(float percent);
bool usesChannel(uint8_t ch) const {
return std::find(_control_channels.begin(), _control_channels.end(), ch) != _control_channels.end();
@ -108,7 +108,8 @@ class DisabledMode : public ModeStrategy {
public:
DisabledMode() {
}
void update() {
NDIS std::expected<void, op::error> update() {
return {};
}
float targetTemperature() const = delete;
void setTargetTemperature(float temp) = delete;
@ -125,7 +126,7 @@ class ConstantTemperatureMode : public ModeStrategy {
ConstantTemperatureMode(ConstantTemperatureMode &&) = default;
ConstantTemperatureMode &operator=(ConstantTemperatureMode &&) = default;
void update();
NDIS std::expected<void, op::error> update();
private:
/// reference wrapper allows this class to be movable
@ -145,7 +146,7 @@ class TemperatureSlopeMode : public ModeStrategy {
TemperatureSlopeMode(TemperatureSlopeMode &&) = default;
TemperatureSlopeMode &operator=(TemperatureSlopeMode &&) = default;
void update();
NDIS std::expected<void, op::error> update();
private:
/// reference wrapper allows this class to be movable
@ -209,8 +210,8 @@ class OperationChannel {
return _ownId;
}
void update() {
std::visit([](auto &mode) { mode.update(); }, _mode);
NDIS std::expected<void, op::error> update() {
return std::visit([](auto &mode) { return mode.update(); }, _mode);
}
NDIS std::expected<void, op::error> handle_targetTemperatureRequest(const TargetTemperatureRequest &request, TargetTemperatureResponse &resp);
@ -247,7 +248,10 @@ class OperationOrchestrator {
NDIS std::expected<void, op::error> channelValid(uint8_t ch) const {
auto found = std::find_if(_channels.begin(), _channels.end(), [&](const OperationChannel &opch) { return opch.id() == ch; });
if (found != _channels.end()) return std::unexpected{op::channel_not_found{}};
if (found == _channels.end()) {
ULOG_WARNING("channel %d not found", ch);
return std::unexpected{op::channel_not_found{}};
}
return {};
}
@ -260,6 +264,7 @@ class OperationOrchestrator {
auto found = std::find_if(_channels.begin(), _channels.end(), [&](const OperationChannel &opch) { return opch.id() == ch; });
if (found == _channels.end()) {
ULOG_WARNING("channel %d not found", ch);
return std::unexpected{op::channel_not_found{}};
}
@ -270,6 +275,7 @@ class OperationOrchestrator {
auto found = std::find_if(_channels.begin(), _channels.end(), [&](const OperationChannel &opch) { return opch.id() == ch; });
if (found == _channels.end()) {
ULOG_WARNING("channel %d not found", ch);
return std::unexpected{op::channel_not_found{}};
}

View File

@ -2,6 +2,7 @@
#include <algorithm>
#include <array>
#include <cassert>
#include <chrono>
#include <cmath>
#include <cstddef>
@ -92,7 +93,9 @@ TemperatureSampler::TemperatureSampler(uint8_t channel) : _temperature{0.05}, _c
k_work_init(&_sampleWork, TemperatureSampler::sample_work_handler);
select_channel(_channel);
calibration();
if (not calibration()) {
ULOG_WARNING("Temperature sampler channel %d calibration failed", _channel);
}
_broadcastTimer.start();
_samplerTimer.start();
@ -227,6 +230,8 @@ float TemperatureSampler::adc2CelciusWeight() const {
const auto adcMax = this->_referenceAdcMax.filtered();
const auto adcMin = this->_referenceAdcMin.filtered();
assert(adcMin != adcMax);
return (tempMax - tempMin) / static_cast<float>(adcMin - adcMax);
}
@ -303,15 +308,25 @@ std::expected<adc_sample, temperature::error> TemperatureSampler::adc() {
constexpr auto ADC_RESOLUTION = 12;
const adc_dt_spec adc_spec = ADC_DT_SPEC_GET_BY_IDX(DT_PATH(zephyr_user), 0);
const auto nsamples = oversampling();
const adc_sequence_options options = {
.interval_us = 100,
.callback = nullptr,
.user_data = nullptr,
.extra_samplings = static_cast<uint16_t>(nsamples - 1),
const adc_sequence_options options = {
.interval_us = 100,
.callback = nullptr,
.user_data = nullptr,
.extra_samplings = static_cast<uint16_t>(nsamples - 1),
};
samples.resize(nsamples, 0);
adc_sequence sequence = {.options = &options, .channels = BIT(15), .buffer = samples.data(), .buffer_size = samples.size() * sizeof(adc_sample), .resolution = ADC_RESOLUTION, .oversampling = 0, .calibrate = false};
const adc_sequence sequence = {//
.options = &options,
.channels = BIT(15),
.buffer = samples.data(),
.buffer_size = samples.size() * sizeof(adc_sample),
.resolution = ADC_RESOLUTION,
.oversampling = 0,
.calibrate = false
};
/*
* @retval -EINVAL If a parameter with an invalid value has been provided.
* @retval -ENOMEM If the provided buffer is to small to hold the results
@ -324,7 +339,6 @@ std::expected<adc_sample, temperature::error> TemperatureSampler::adc() {
* in the buffer, but at least some of them were taken with
* an extra delay compared to what was scheduled.
*/
auto ret = adc_read_dt(&adc_spec, &sequence);
if (ret != 0) {
if (not _faultTP) {

View File

@ -111,7 +111,7 @@ class TemperatureSampler {
TemperatureSampler &operator=(TemperatureSampler &&) = delete;
void tick() ;
std::expected<void, temperature::error> calibration() ;
NDIS std::expected<void, temperature::error> calibration() ;
bool pending() const {
return _samplePending;
@ -134,8 +134,8 @@ class TemperatureSampler {
float adc2CelciusWeight() const;
Temperature_c adcToTemperature(uint32_t adc_value) const;
std::expected<adc_sample, temperature::error> adcWithCalibration();
std::expected<adc_sample, temperature::error> adc();
NDIS std::expected<adc_sample, temperature::error> adcWithCalibration();
NDIS std::expected<adc_sample, temperature::error> adc();
void do_sample() ;
struct k_work _sampleWork{};
@ -191,14 +191,13 @@ class TemperatureSamplerOrchestrator {
TemperatureSamplerOrchestrator();
void loop();
[[nodiscard]] std::expected<void, temperature::error> handle_getTemperatureRequest(const GetTemperatureRequest &req, GetTemperatureResponse &resp) const;
[[nodiscard]] std::expected<void, temperature::error> handle_activeChannelsRequest(const GetActiveChannelsRequest &req, GetActiveChannelsResponse &resp) const;
[[nodiscard]] std::expected<void, temperature::error> handle_samplerConfigRequest(const SamplerConfigRequest &req, SamplerConfigResponse &resp);
NDIS std::expected<void, temperature::error> handle_getTemperatureRequest(const GetTemperatureRequest &req, GetTemperatureResponse &resp) const;
NDIS std::expected<void, temperature::error> handle_activeChannelsRequest(const GetActiveChannelsRequest &req, GetActiveChannelsResponse &resp) const;
NDIS std::expected<void, temperature::error> handle_samplerConfigRequest(const SamplerConfigRequest &req, SamplerConfigResponse &resp);
private:
// events handled in main loop
void event_messageArrived();
void action_samplersTick();
std::array<TemperatureSampler, temperatureChannelsNr> _temperatureSamplerChannels;