Zephyr Modern C++ - Statemachines using std::variant/std::visit

Can we use the latest C++ features on microcontrollers? Yes.

I’ve been testing out to use it on Zephyr and ESP-IDF

One particular feature I tried out in this example is the use of std::variant for different States, and ultimately using std::visit to iterate through them. Here I provide implementation of a general get_state_info to read what the current state is using the Overloaded Lambda idiom (to enforce availability of all StateVariants), it will then return a std::string_view of different messages based on the current_state_

Let’s say, a more shiny Statemachine haha. A different approach than using bare enums and switch statements (not simply saying they are less)

Check it out here!

github.com/royyandzakiy/zephyr-modern-cpp

enum class StateId {
	IDLE,
	MONITORING,
	ALERT,
	CALIBRATING
};
struct IdleState {};
struct MonitoringState {
	int32_t average_temp_value;
	int sample_count;
};

struct AlertState {
	std::string_view message;
	int32_t threshold_temp_value;
};

struct CalibratingState {
	int32_t reference_temp_value;
	int calibration_step;
};

using StateVariant = std::variant<std::monostate, IdleState, MonitoringState, AlertState, CalibratingState>;

class StateMachine {
    // ...
    auto get_state_info() const -> std::string_view {
		return std::visit(Overloaded{
			[this](const IdleState&) -> std::string_view {
				return "Status: Idle - Awaiting for sensor trigger"sv;
			},
			[this](const MonitoringState& s) -> std::string_view {
				// psst: not using std::format because the zephyr SDK does not have #include <format>
				int len = snprintf(format_buffer_.data(), format_buffer_.size(), 
									"Status: Monitoring [Avg: %d.%02d°C | Samples: %d]",
									ipart(s.average_temp_value), fpart(s.average_temp_value), s.sample_count);
				return { format_buffer_.data(), static_cast<size_t>(len) };
			},
			[this](const AlertState& s) -> std::string_view {
				int len = snprintf(format_buffer_.data(), format_buffer_.size(), 
								"Status: ALERT [%.*s | Threshold: %d.%02d°C]", 
								static_cast<int>(s.message.length()), s.message.data(),
								ipart(s.threshold_temp_value), fpart(s.threshold_temp_value));
				return {format_buffer_.data(), static_cast<size_t>(len)};
			},
			[this](const CalibratingState& s) -> std::string_view {
				int len = snprintf(format_buffer_.data(), format_buffer_.size(), 
								"Status: Calibrating [Ref: %d.%02d°C | Step: %d/5]", 
								ipart(s.reference_temp_value), fpart(s.reference_temp_value), s.calibration_step);
				return {format_buffer_.data(), static_cast<size_t>(len)};
			},
			[this](const auto&) -> std::string_view {
				// default fallback
				return "Status: Unknown State! [Error]"sv;
			}
		}, current_state_);
	}
    // ...
};

auto main() -> int {
    StateMachine sm_;
	TemperatureSensor t_;
	HumiditySensor h_;
	PressureSensor p_;
    
    sm_.process_sensors(t_, h_, p_);

    // ...

    auto info = sm_.get_state_info();

    return 0;
}

Discussion and feedback