2026-05-11 (3VO): C++: Inheritance (Mocking A Sensor)#
Initial State: Reading And Writing sysfs Files#
Hardware based implementation: we are reading and writing
sysfsfilesSensor:
hwmonstyle; reading, say,/sys/class/hwmon/hwmon2/temp1_inputPWM: writing, say,
/sys/class/pwm/pwmchip0/pwm0/periodand/sys/class/pwm/pwmchip0/pwm0/duty_cycle
Existing Pieces: PWM Pin#
#pragma once
#include <filesystem>
class PWMPin
{
public:
PWMPin(std::filesystem::path basedir);
void set_period(uint64_t);
void set_duty_cycle(uint64_t);
private:
std::filesystem::path _basedir;
bool _ok;
};
#include "pwm.h"
#include "fileutil.h"
PWMPin::PWMPin(std::filesystem::path basedir)
: _basedir(basedir)
{
if (!std::filesystem::exists(basedir))
throw std::exception();
if (!file_is_rwable(basedir / "period"))
throw std::exception();
if (!file_is_rwable(_basedir / "duty_cycle"))
throw std::exception();
}
void PWMPin::set_period(uint64_t period)
{
return write_uint64_t_to_file(_basedir / "period", period);
}
void PWMPin::set_duty_cycle(uint64_t duty_cycle)
{
return write_uint64_t_to_file(_basedir / "duty_cycle", duty_cycle);
}
Existing Pieces: Sensor#
#pragma once
#include <filesystem>
class Sensor
{
public:
Sensor(const std::filesystem::path& temperature_file);
double get_temperature();
private:
std::filesystem::path _temperature_file;
};
#include "sensor.h"
#include "fileutil.h"
Sensor::Sensor(const std::filesystem::path& temperature_file)
: _temperature_file(temperature_file) {}
double Sensor::get_temperature()
{
return read_file_as_uint64_t(_temperature_file) / 1000;
}
Existing Pieces: Main Program#
code/2026-05-11/initial/temperature-display.cpp##include "sensor.h"
#include "pwm.h"
#include "logic.h"
#include "sensor.h"
#include "pwm.h"
#include <iostream>
int main(int argc, char** argv)
{
if (argc != 3) {
std::cerr << "Usage: " << argv[0] << " <SYSFS-HWMON-STYLE-TEMPERATURE-FILE> <SYSFS-PWMPIN-DIRECTORY>" << std::endl;
return 1;
}
Sensor sensor(argv[1]);
PWMPin pwmpin(argv[2]);
pwmpin.set_period(30*1000*1000);
pwmpin.set_duty_cycle(0);
Logic logic(&sensor, &pwmpin);
logic.loop();
return 0;
}
Existing Pieces: Loop#
#pragma once
#include "sensor.h"
#include "pwm.h"
class Logic
{
public:
Logic(
Sensor* sensor,
PWMPin* pwmpin
);
void loop();
private:
Sensor* _sensor;
PWMPin* _pwmpin;
};
#include "logic.h"
#include <cassert>
Logic::Logic(
Sensor* sensor,
PWMPin* pwmpin)
: _sensor(sensor),
_pwmpin(pwmpin) {}
void Logic::loop()
{
while (true) {
double t = _sensor->get_temperature();
uint64_t duty_cycle = t/50 * 30*1000*1000;
_pwmpin->set_duty_cycle(duty_cycle);
const timespec naptime = {
.tv_sec = 1,
.tv_nsec = 0,
};
int rv = nanosleep(&naptime, nullptr);
assert(rv != -1);
}
}
And Sensor/PWM Alternatives?#
Unplanned for alternatives:
Logichardwired to exactly thesesysfshardware interfacePossible alternative sensors
Using
/dev/i2c-1(implementing an I2C sensor’s or PWM controller’s protocol in userspace)Likewise,
/dev/spi*User Space IO (UIO) driver (https://www.kernel.org/doc/html/latest/driver-api/uio-howto.html)
…
Possible Sensor Alternative (Brute Force Approach)#
Sensor type is determined at startup: choose alternative implementation instead
Let logic operate on that instead
Intended modification: instantiate alternatives
#include "alternative-sensor.h"
#include "pwm.h"
#include "logic.h"
#include <iostream>
int main(int argc, char** argv)
{
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <SYSFS-PWMPIN-DIRECTORY>" << std::endl;
return 1;
}
AlternativeSensor sensor; // <-- intended modification
PWMPin pwmpin(argv[1]);
pwmpin.set_period(30*1000*1000);
pwmpin.set_duty_cycle(0);
Logic logic(&sensor, &pwmpin);
logic.loop();
return 0;
}
Btw, The Alternative Sensor Implementation#
#pragma once
class AlternativeSensor
{
public:
double get_temperature() { return 37.5; }
};
Alternative: Unintended Modifications#
#pragma once
#include "alternative-sensor.h"
#include "pwm.h"
class Logic
{
public:
Logic(
AlternativeSensor* sensor, // <-- unintended modification
PWMPin* pwmpin
);
void loop();
private:
AlternativeSensor* _sensor; // <-- unintended modification
PWMPin* _pwmpin;
};
A Better Approach For Alternatives: Interfaces/Polymorphic Types#
SensorInterfacedoes not implement anythingJust dictates how an implementation must look like
⟶ Abstract base class
Logicuses the interface - and not a concrete implementationImplementations implement that interface
⟶ Implementations can be exchanged, while leaving
Logicunmodified
Defining An Interface#
#pragma once
class SensorInterface
{
public:
virtual ~SensorInterface() = default; // <-- wtf?
virtual double get_temperature() = 0; // <-- "abstract", or "pure virtual"
};
Interfaces are only one usage of C++’s inheritance toolcase
virtual: dynamic dispatchAlthough called (by
Logic) via the base class reference/pointer, the call is dispatched to the actual type= 0: abstract methodNo implementation given ⟶ containing class cannot be instantiated
Virtual destructor ⟶ see Destructors And Interfaces
Implementing An Interface#
#pragma once
#include "sensor-interface.h"
#include <filesystem>
class Sensor : public SensorInterface // <-- *is-a* SensorInterface
{
public:
Sensor(const std::filesystem::path& temperature_file);
double get_temperature() override; // <-- override? wtf?
private:
std::filesystem::path _temperature_file;
};
#pragma once
#include "sensor-interface.h"
class AlternativeSensor : public SensorInterface // <-- *is-a* SensorInterface
{
public:
double get_temperature() override // <-- override? wtf?
{
return 37.5;
}
};
Assembling Parts#
Instantiate concrete type
Abstract types cannot be instantiated
Pass concrete object as their base class
⟶ Automatically converted to their base types
#include "alternative-sensor.h"
#include "pwm.h"
#include "logic.h"
#include <iostream>
int main(int argc, char** argv)
{
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <SYSFS-PWMPIN-DIRECTORY>" << std::endl;
return 1;
}
AlternativeSensor sensor; // <-- instantiation of concrete type
PWMPin pwmpin(argv[1]);
pwmpin.set_period(30*1000*1000);
pwmpin.set_duty_cycle(0);
Logic logic(
&sensor, // <-- AlternativeSensor* converted to SensorInterface*
&pwmpin);
logic.loop();
return 0;
}
Final Polishing: Names Are Important#
Sensordoes not say anything about its natureIt is actually Linux specific
Of the many Linux ways to talk to a sensor, it uses the
hwmon(hardware monitoring) subsystemLinuxHWMONSensoris probably a better name
AlternativeSensorhas been chosen for didactic purposes, and also says nothing about the nature of the alternativeWe want to use such a trivial implementation for automatic tests (
Logicdoes not require actual hardware to be tested)MockSensordescribes that fact
SensorInterface. Interfaces are an important concept, and the name reflects it well.It just does not roll well off the tongue
Why not just say
Sensor?
Before |
After |
|
|