Datalogger: A Semi-Realistic Project (Threaded Version)#
Add Second Sensor (Message Queue)#
Add second sensor
⟶ From interrupt, communicate sensor name
Attention
std::string_view does not own ⟶ Lifetime!
#include <why.h>
#include <why-sensor.h>
#include <why-irq.h>
#include <why-messagequeue.h>
#include <why-time.h>
#include <string>
struct sensor_data
{
Why::TimeSpec timestamp;
std::string_view sensorname;
uint64_t value;
};
int main()
{
Why::init();
Why::RandomSensor s1(0, 100);
Why::RandomSensor s2(100, 200);
auto sensor_data_queue = Why::MessageQueue<sensor_data>::create();
assert(sensor_data_queue);
// activate sensor interrupts
{
auto isr = [&sensor_data_queue, &s1, &s2](int irqnum){
sensor_data data;
if (irqnum == 7)
data = {
.timestamp=Why::now_monotonic(),
.sensorname="s1", // <-- std::string_view lifetime!
.value=s1.get_value(),
};
else if (irqnum == 42)
data = {
.timestamp=Why::now_monotonic(),
.sensorname="s2", // <-- std::string_view lifetime!
.value=s2.get_value(),
};
else
assert(!"unexpected irq");
auto ok = sensor_data_queue->put_noblock(data);
assert(ok);
};
Why::IRQ::connect(7, isr);
Why::IRQ::connect(42, isr);
}
// consume data
while (true) {
auto sample = sensor_data_queue->get();
assert(sample);
auto [timestamp, sensorname, value] = *sample;
std::println("timestamp={}, sensorname={}, value={}", timestamp.to_seconds(), sensorname, value);
}
return 0;
}
Add Watchdog#
Shift sensor data consumption into a dedicated thread
Feed Watchdog in main thread (which has highest priority)
#include <why.h>
#include <why-sensor.h>
#include <why-irq.h>
#include <why-messagequeue.h>
#include <why-time.h>
#include <why-watchdog.h>
#include <why-thread.h>
#include <string>
struct sensor_data
{
Why::TimeSpec timestamp;
std::string_view sensorname;
uint64_t value;
};
int main()
{
Why::init();
Why::RandomSensor s1(0, 100);
Why::RandomSensor s2(100, 200);
auto sensor_data_queue = Why::MessageQueue<sensor_data>::create();
assert(sensor_data_queue);
// consume data in dedicated thread
auto sensor_data_consumer = Why::Thread::create(
[&sensor_data_queue](){
while (true) {
auto sample = sensor_data_queue->get();
assert(sample);
auto [timestamp, sensorname, value] = *sample;
std::println("timestamp={}, sensorname={}, value={}", timestamp.to_seconds(), sensorname, value);
}
});
assert(sensor_data_consumer);
// activate sensor interrupts
{
auto isr = [&sensor_data_queue, &s1, &s2](int irqnum){
sensor_data data;
if (irqnum == 7)
data = {.timestamp=Why::now_monotonic(), .sensorname="s1", .value=s1.get_value()};
else if (irqnum == 42)
data = {.timestamp=Why::now_monotonic(), .sensorname="s2", .value=s2.get_value()};
else
assert(!"unexpected irq");
auto ok = sensor_data_queue->put_noblock(data);
assert(ok);
};
Why::IRQ::connect(7, isr);
Why::IRQ::connect(42, isr);
}
// activate and feed watchdog
const Why::TimeSpec watchdog_timeout(2, 0);
Why::Watchdog::activate(watchdog_timeout);
while (true) {
Why::Watchdog::feed();
Why::sleep(watchdog_timeout - Why::TimeSpec(0, 500'000'000));
}
return 0;
}
Add CSV And MQTT#
Add datalogger functionality to consumer thread
Write CSV:
Why::FilePublish on an MQTT topic:
Why::MQTTPublisher
Note
Both File IO and network IO are notoriously blocking!
#include <why.h>
#include <why-sensor.h>
#include <why-irq.h>
#include <why-messagequeue.h>
#include <why-time.h>
#include <why-watchdog.h>
#include <why-thread.h>
#include <why-file.h>
#include <why-mqtt.h>
#include <string>
#include <format>
struct sensor_data
{
Why::TimeSpec timestamp;
std::string_view sensorname;
uint64_t value;
};
int main()
{
Why::init();
Why::RandomSensor s1(0, 100);
Why::RandomSensor s2(100, 200);
auto sensor_data_queue = Why::MessageQueue<sensor_data>::create();
assert(sensor_data_queue);
auto csv_file = Why::File::open("data.csv", Why::File::WriteOnly|Why::File::Append|Why::File::Create);
assert(csv_file);
auto mqtt = Why::MQTTPublisher::create("why-topic", "127.0.0.1");
assert(mqtt);
// consume data in dedicated thread
auto sensor_data_consumer = Why::Thread::create(
[&sensor_data_queue, &csv_file, &mqtt](){
while (true) {
auto sample = sensor_data_queue->get();
assert(sample);
auto [timestamp, sensorname, value] = *sample;
// write to CSV
std::string line = std::format("{};{};{}\n", timestamp.to_seconds(), sensorname, value);
auto written = csv_file->write((const uint8_t*)line.c_str(), line.size());
assert(written);
// publish MQTT
std::string json = std::format("{{\"timestamp\": {}, \"sensorname\": {}, \"value\": {} }}",
timestamp.to_seconds(), sensorname, value);
auto ok = mqtt->publish(json);
assert(ok);
}
});
assert(sensor_data_consumer);
// activate sensor interrupts
{
auto isr = [&sensor_data_queue, &s1, &s2](int irqnum){
sensor_data data;
if (irqnum == 7)
data = {.timestamp=Why::now_monotonic(), .sensorname="s1", .value=s1.get_value()};
else if (irqnum == 42)
data = {.timestamp=Why::now_monotonic(), .sensorname="s2", .value=s2.get_value()};
else
assert(!"unexpected irq");
auto ok = sensor_data_queue->put_noblock(data);
assert(ok);
};
Why::IRQ::connect(7, isr);
Why::IRQ::connect(42, isr);
}
// activate and feed watchdog
const Why::TimeSpec watchdog_timeout(2, 0);
Why::Watchdog::activate(watchdog_timeout);
while (true) {
Why::Watchdog::feed();
Why::sleep(watchdog_timeout - Why::TimeSpec(0, 500'000'000));
}
return 0;
}