forked from EmbeddedTeam/app_photomagnetic
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.
140 lines
4.6 KiB
C++
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
|