test_led_strip/include/infrared.hpp
zhangyisong 0e5d15c814 Refactor app to use static classes and event-driven callbacks
Restructure Led as a template on the DT spec, add a generic
signal/slot mechanism for host status, and cache Infrared sensor
samples via trigger callbacks. Add new init modules for LED, temp,
and watchdog, and build them into the main app.
2026-08-03 22:12:31 +08:00

140 lines
4.6 KiB
C++

#ifndef __THER_INFRARED_HPP__
#define __THER_INFRARED_HPP__
#include <etl/algorithm.h>
#include <etl/array.h>
#include <etl/delegate.h>
#include <etl/tuple.h>
#include <utility>
#include <zephyr/device.h>
#include <zephyr/drivers/sensor.h>
#include <zpp/result.hpp>
#include <zpp/value.hpp>
#include <zpp/work_queue.hpp>
namespace ther {
class Infrared {
public:
static auto Init() -> zpp::error {
return InitImpl(std::make_index_sequence<s_dev.size()>{});
}
/* Register a callback invoked every scan period (20 ms) with the
* hottest sensor value. Runs in the system workqueue thread context -
* keep the callback lightweight (no blocking, no SPI). */
static auto
AddCallbackWhenSensorValueReady(etl::delegate<void(sensor_value)> cb)
-> zpp::error {
if (s_cb.is_valid()) {
return zpp::error_code::k_busy;
}
s_cb = cb;
return zpp::ok();
}
/* Hottest sensor among the latest cached samples (no polling: the
* trigger callback keeps the cache fresh). */
static auto GetMaxSensorValue() -> zpp::result<std::pair<int, sensor_value>> {
bool found = false;
int max_id = 0;
sensor_value max_val{};
for (size_t id = 0; id < s_dev.size(); ++id) {
if (!s_latest_valid[id]) {
continue;
}
const sensor_value val{s_latest_val1[id], s_latest_val2[id]};
if (!found || val.val1 > max_val.val1 ||
(val.val1 == max_val.val1 && val.val2 > max_val.val2)) {
found = true;
max_id = static_cast<int>(id);
max_val = val;
}
}
if (!found) {
return zpp::error_code::k_nodata;
}
return std::make_pair(max_id, max_val);
}
/* Snapshot of all cached sensor values (pure memory reads, safe in
* trigger context). Channels without a sample yet read as 0.
* auto return: the body is parsed in complete-class context where the
* trailing members (s_dev, caches) are visible. */
static auto GetAllSensorValues() {
etl::array<sensor_value, s_dev.size()> out{};
for (size_t id = 0; id < s_dev.size(); ++id) {
if (s_latest_valid[id]) {
out[id] = sensor_value{s_latest_val1[id], s_latest_val2[id]};
}
}
return out;
}
private:
/* Per-sensor trigger handler; the channel index is a compile-time
* constant. Runs in the UART trigger context (workqueue thread for
* CH9438 ports, ISR context for on-chip UARTs) - keep it lock-free. */
template <size_t I>
static void OnSensorDataReady(const struct device *dev,
const struct sensor_trigger *trig) {
sensor_value val;
if (0 != sensor_channel_get(dev, SENSOR_CHAN_AMBIENT_TEMP, &val)) {
printk("[infra] ch%u channel_get failed\n", (unsigned)I);
return;
}
s_latest_val1[I] = val.val1;
s_latest_val2[I] = val.val2;
s_latest_valid[I] = true;
if (s_cb.is_valid()) {
s_cb(MaxCachedValue()); /* notify with the hottest sensor value */
}
}
/* Latest cached max value; pure memory reads, safe in trigger context. */
static auto MaxCachedValue() -> sensor_value {
bool found = false;
sensor_value max_val{};
for (size_t id = 0; id < s_dev.size(); ++id) {
if (!s_latest_valid[id]) {
continue;
}
const sensor_value val{s_latest_val1[id], s_latest_val2[id]};
if (!found || val.val1 > max_val.val1 ||
(val.val1 == max_val.val1 && val.val2 > max_val.val2)) {
found = true;
max_val = val;
}
}
return max_val;
}
template <size_t... Is>
static auto InitImpl(std::index_sequence<Is...>) -> zpp::error {
static sensor_trigger tri{.type = SENSOR_TRIG_DATA_READY,
.chan = SENSOR_CHAN_AMBIENT_TEMP};
int ret = 0;
((printk("[infra] ch%u ready=%d\n", (unsigned)Is,
device_is_ready(s_dev[Is])),
ret |= sensor_trigger_set(s_dev[Is], &tri, &OnSensorDataReady<Is>)),
...);
return ret != 0 ? zpp::error{-ENODEV} : zpp::ok();
}
#define INFRARED_DEV(node) DEVICE_DT_GET(node),
inline static etl::array s_dev{
DT_FOREACH_STATUS_OKAY(godtek_temp_uart, INFRARED_DEV)};
/* Field-level volatile caches: written by the trigger handlers, read by
* GetMaxSensorValue. sensor_value itself cannot be volatile (no volatile
* copy/assign), so the two 32-bit fields are cached separately. A torn
* cross-field read is possible but negligible for slowly changing temps. */
inline static volatile int32_t s_latest_val1[s_dev.size()]{};
inline static volatile int32_t s_latest_val2[s_dev.size()]{};
inline static volatile bool s_latest_valid[s_dev.size()]{};
inline static etl::delegate<void(sensor_value)> s_cb;
};
} // namespace ther
#endif