c17 trait experiments

This commit is contained in:
lovebird 2025-05-23 16:53:09 +02:00
parent c1cc2895e5
commit 5a8b87fa74
2 changed files with 347 additions and 0 deletions

130
experiments/main.c Normal file
View File

@ -0,0 +1,130 @@
// component_networked_demo.cpp ---------------------------------------------
#include <cstdint>
#include <cstddef>
#include <array>
#include <type_traits>
#include <ArduinoLog.h>
/* ────────────────────────────────── 0. Build flag → constexpr ──────────── */
#if defined(ENABLE_MODBUS) && ENABLE_MODBUS
#define kEnableModbus true
#else
#endif
/* ────────────────────────────────── 1. Helper: maybebase ──────────────── */
struct dummy_base {};
template <typename Base, bool Enable>
using maybe_base_t =
typename std::conditional<Enable, Base, dummy_base>::type;
/* ────────────────────────────────── 2. Core contracts ──────────────────── */
struct Component {
virtual void setup() = 0;
virtual void loop() = 0;
virtual ~Component() = default;
};
struct RegisterMap {
const uint16_t* data;
std::size_t size;
};
struct Networked {
virtual RegisterMap registerMapping() const = 0;
virtual uint16_t deviceAddress() const { return 1; }
virtual void onRegisterWrite(uint16_t, uint16_t) {}
virtual ~Networked() = default;
};
/* ────────────────────────────────── 3. ComponentNetworked ──────────────── */
// Carries Networked only when kEnableModbus == true
using NetworkBase = maybe_base_t<Networked, kEnableModbus>;
class ComponentNetworked : public Component, public NetworkBase {
/* purely a glue; still abstract if Networked is really inherited */
};
/* ────────────────────────────────── 4. TemperatureSensorNetworked ─────── */
// Two specialisations the compiler picks one at translation time
template <bool Enable>
class TemperatureSensorNetworkedImpl;
/* ----- 4.a NONnetworked flavour --------------------------------------- */
template <>
class TemperatureSensorNetworkedImpl<false> : public ComponentNetworked
{
public:
void setup() override { Log.notice(F("[Temp<%d>] setup (lean)\n"), kEnableModbus); }
void loop() override { /* Log.verb(F("[Temp<%d>] loop (lean)\n"), kEnableModbus); */ }
float readCelsius() const { return 25.0f; }
};
/* ----- 4.b Networkcapable flavour ------------------------------------- */
template <>
class TemperatureSensorNetworkedImpl<true> : public Component, public Networked
{
std::array<uint16_t, 2> regs_{ 250 /*25 °C×10*/, 0 };
public:
/* Component part */
void setup() override { Log.notice(F("[Temp<%d>] setup (net)\n"), kEnableModbus); }
void loop() override { /* Log.verb(F("[Temp<%d>] loop (net)\n"), kEnableModbus); */ }
/* Networked part */
RegisterMap registerMapping() const override {
return { regs_.data(), regs_.size() };
}
uint16_t deviceAddress() const override { return 42; }
void onRegisterWrite(uint16_t r, uint16_t v) override {
if (r < regs_.size()) regs_[r] = v;
}
float readCelsius() const { return regs_[0] / 10.0f; }
};
/* ----- 4.c Public alias ------------------------------------------------- */
using TemperatureSensorNetworked =
TemperatureSensorNetworkedImpl<kEnableModbus>;
/* ────────────────────────────────── 5. Arduino setup/loop ───────────── */
// Global instance of the sensor
TemperatureSensorNetworked dev;
void setup() {
// Initialize Serial and Logging
Serial.begin(115200);
while (!Serial && millis() < 3000) { delay(100); } // Wait for serial port connection
Log.begin(LOG_LEVEL_VERBOSE, &Serial);
Log.notice(F("\n--- Logging Initialized ---\n"));
Log.notice(F("kEnableModbus = %d\n"), kEnableModbus);
dev.setup();
// Conditionally compile the network check using the preprocessor
#if kEnableModbus
Log.notice(F("Network features compiled.\n"));
// Check if the resolved type actually inherits from Networked
if (std::is_base_of<Networked, TemperatureSensorNetworked>::value) {
Log.notice(F(" Device IS Networked. Address: %d, Registers:"), dev.deviceAddress());
auto map = dev.registerMapping();
for (std::size_t i = 0; i < map.size; ++i) Log.notice(F(" %d"), map.data[i]);
Log.notice(F("\n"));
} else {
Log.warning(F(" Device IS NOT Networked (type check failed).\n"));
}
#else
Log.notice(F("Network features NOT compiled.\n"));
// Demonstrate accessing non-networked specific method
Log.notice(F(" Device reading (lean): %.1f C\n"), dev.readCelsius());
#endif
}
void loop() {
dev.loop();
}

217
experiments/main.cpp Normal file
View File

@ -0,0 +1,217 @@
// feature_di_component_demo.cpp -------------------------------------------
// Build the "lean" variant : g++ -std=c++20 -O2 -DDEMO_MAIN feature_di_component_demo.cpp -o demo
// Build the "Modbus" variant: g++ -std=c++20 -O2 -DDEMO_MAIN -DENABLE_MODBUS feature_di_component_demo.cpp -o demo_net
// @link ://chatgpt.com/share/6803c6b7-0468-8001-857f-352a31c8c9bd
// ───────────────────────────────────────────────────────────────────────────
#include <Arduino.h> // Added for Serial
#include <cstdint>
#include <cstddef>
#include <array>
// #include <iostream> // Replaced by Arduino.h
#include <type_traits>
#include <string_view> // For info() return type example
#include <cstring> // For strcmp
/*──────────────────── 1. Tiny utility -----------------------------------*/
struct RegisterMap {
const uint16_t* data;
std::size_t size;
};
/*──────────────────── 2. "Feature" concept (any module that has setup/loop/info)*/
// Renamed and updated SFINAE check
template<typename, typename = std::void_t<>>
struct is_valid_feature : std::false_type {};
template<typename F>
struct is_valid_feature<F, std::void_t<
decltype(std::declval<F&>().setup()),
decltype(std::declval<F&>().loop()),
decltype(std::declval<const F&>().info()) // Added const info() check
>> : std::true_type {};
// Updated helper function
template<typename F> constexpr bool is_valid_feature_func() { return is_valid_feature<F>::value; }
/*──────────────────── 3. Individual feature packs -----------------------*/
// 3a. Core sensor logic ────────────────────────────────────────────────
struct CoreFeature {
static constexpr bool has_network = false;
void setup() { Serial.println(F("[Core] setup")); } // Use Serial
void loop () { /*Serial.println(F("[Core] loop"));*/ } // Commented out for less noise
// Added info method
void info() const { Serial.println(F("[Core] Info: Basic temperature sensing.")); } // Use Serial
float readCelsius() const { return 25.0f; }
};
// 3b. Optional Modbus/Network feature ──────────────────────────────────
struct ModbusFeature {
static constexpr bool has_network = true;
std::array<uint16_t,2> regs{ 250 /*25 °C×10*/, 0 };
void setup() { Serial.println(F("[Modbus] setup")); } // Use Serial
void loop () { /*Serial.println(F("[Modbus] loop"));*/ } // Commented out for less noise
// Added info method
void info() const { // Use Serial
Serial.print(F("[Modbus] Info: Modbus TCP/IP enabled. Address: "));
Serial.println(deviceAddress());
}
RegisterMap registerMapping() const { return { regs.data(), regs.size() }; }
uint16_t deviceAddress() const { return 42; }
void onRegisterWrite(uint16_t r, uint16_t v) {
if (r < regs.size()) regs[r] = v;
}
};
// 3c. Default Logger feature ─────────────────────────────────────────────
struct LoggerFeature {
static constexpr bool has_network = true; // Assuming logger might send logs over network
void setup() { Serial.println(F("[Logger] setup")); } // Use Serial
void loop () { /*Serial.println(F("[Logger] loop"));*/ } // Commented out for less noise
// Added info method
void info() const { Serial.println(F("[Logger] Info: Basic console logging active.")); } // Use Serial
};
/*──────────────────── 4. Generic "Device" aggregator ───────────────────*/
// Use static_assert with the updated SFINAE check
// Inherit LoggerFeature by default
template<typename... Fs>
class Device : public LoggerFeature, public Fs...
{
// Ensure the default and all *additional* base classes Fs satisfy the requirements
static_assert(is_valid_feature_func<LoggerFeature>(), "Default LoggerFeature must implement setup(), loop(), and info()");
// Use C++17 fold expression within the static_assert for additional features
static_assert(sizeof...(Fs) == 0 || (is_valid_feature_func<Fs>() && ...), "All additional features must implement setup(), loop(), and info()");
public:
void setup() {
LoggerFeature::setup(); // Explicitly call setup for the default feature
if constexpr (sizeof...(Fs) > 0) { // Check if there are other features
(Fs::setup(), ...); // Call setup for the rest using fold-expression
}
}
void loop () {
LoggerFeature::loop(); // Explicitly call loop for the default feature
if constexpr (sizeof...(Fs) > 0) {
(Fs::loop (), ...); // Call loop for the rest
}
}
// Added aggregated info method
void info() const {
// Call info() for LoggerFeature and all Fs... using a fold expression
(LoggerFeature::info(), (Fs::info(), ...));
}
// Include LoggerFeature's network status in the calculation
static constexpr bool has_network = LoggerFeature::has_network || (false || ... || Fs::has_network);
};
/*──────────────────── 5. Choose the feature bundle (application layer) ───*/
// LoggerFeature is now implicitly included by Device
#ifdef ENABLE_MODBUS
using TemperatureSensor = Device<CoreFeature, ModbusFeature>;
#else
using TemperatureSensor = Device<CoreFeature>;
#endif
/*──────────────────── Arduino Entry Points -------------------------------*/
// Instantiate the device globally
TemperatureSensor device;
// Buffer for serial commands
#define SERIAL_CMD_BUFFER_SIZE 64
char serialCmdBuffer[SERIAL_CMD_BUFFER_SIZE];
uint8_t serialCmdBufferIdx = 0;
// Helper function to print commands
void printHelp() {
Serial.println(F("--- Serial Commands ---"));
Serial.println(F("1 - Show device info"));
Serial.println(F("? - Print this help message"));
}
void setup() {
// Initialize Serial for logging, if needed (optional)
Serial.begin(115200);
// Wait for serial port to connect (needed for native USB)
// On some boards, this delay is needed to allow the Serial Monitor to connect
// On others (like ESP32), Serial begins immediately. Adjust if needed.
delay(1000);
while (!Serial && millis() < 3000); // Wait up to 3 seconds
Serial.println(F("\n--- Serial Interface Ready ---"));
printHelp();
// <<< Hypothetical serial connection point >>>
Serial.println(F("--- Device Info ---"));
device.info(); // Call the aggregated info method
Serial.println(F("--- Device Setup ---"));
device.setup(); // Call the aggregated setup
Serial.println(F("--- Setup Complete ---"));
}
void loop() {
device.loop(); // Call the aggregated loop
// --- Handle Serial Input ---
while (Serial.available() > 0) {
char receivedChar = Serial.read();
// Handle backspace
if (receivedChar == '\b' || receivedChar == 127) {
if (serialCmdBufferIdx > 0) {
serialCmdBufferIdx--;
Serial.print("\b \b"); // Move cursor back, print space, move back again
}
continue; // Continue to next character
}
// Echo character back (optional, good for interactive use)
Serial.print(receivedChar);
// Process command on newline or carriage return
if (receivedChar == '\n' || receivedChar == '\r') {
serialCmdBuffer[serialCmdBufferIdx] = '\0'; // Null-terminate the string
Serial.println(); // Move to next line after command received
if (serialCmdBufferIdx > 0) { // Process if buffer is not empty
// --- Process Commands ---
if (strcmp(serialCmdBuffer, "1") == 0) {
Serial.println(F("--- Device Info ---"));
device.info();
} else if (strcmp(serialCmdBuffer, "?") == 0) {
printHelp();
} else {
Serial.print(F("Unknown command: '"));
Serial.print(serialCmdBuffer);
Serial.println(F("'"));
printHelp(); // Show help on unknown command
}
}
// Reset buffer index for the next command
serialCmdBufferIdx = 0;
serialCmdBuffer[0] = '\0';
} else if (serialCmdBufferIdx < SERIAL_CMD_BUFFER_SIZE - 1) {
// Add character to buffer if it's not newline/CR and buffer has space
serialCmdBuffer[serialCmdBufferIdx++] = receivedChar;
}
// Ignore characters if buffer is full until newline/CR is received
}
// Add delay or other logic if needed for stability
// delay(10);
}
// ───────────────────────────────────────────────────────────────────────────