Case Study/Livehacking: Heating Control (Reading Sensors)¶
Step 1: Monolithic¶
Hack
SensorReader
Depending on concrete classes (logging, value store)
All in one
main()
file
#include <sensor-const.h>
#include <sensor-random.h>
#include <chrono>
#include <thread>
#include <vector>
#include <map>
#include <iostream>
using namespace std::chrono_literals;
class DemoLogger
{
public:
void log(const std::string& msg)
{
std::cerr << "DEMO-LOGGER: " << msg << std::endl;
}
};
class DemoValueStore
{
public:
void set(const std::string& name, double value)
{
std::cerr << "DEMO-STORE: setting " << name << " = " << value << std::endl;
}
private:
std::map<std::string/*name*/, double/*temperature*/> _store;
};
class SensorReader
{
public:
using NamedSensor = std::pair<std::string, Sensor*>;
using Sensors = std::vector<NamedSensor>;
public:
SensorReader(
const Sensors& sensors,
DemoLogger& logger,
DemoValueStore& store)
: _sensors(sensors),
_logger(logger),
_value_store(store) {}
void doit()
{
for (auto [name, sensor]: _sensors){
_logger.log(name);
double temperature = sensor->get_temperature();
_value_store.set(name, temperature);
}
}
private:
std::vector<NamedSensor> _sensors;
DemoLogger& _logger;
DemoValueStore& _value_store;
};
int main()
{
DemoLogger logger;
DemoValueStore store;
SensorReader::Sensors sensors{
{"sensorA", new RandomSensor(34.2, 41.3)},
{"sensorB", new ConstantSensor(4)},
{"sensorC", new RandomSensor(100, 200000)},
};
SensorReader rdr(
sensors,
logger,
store
);
for (auto round: {1,2,3,4,5}) {
std::cout << "*** Round " << round << " ..." << std::endl;
rdr.doit();
std::this_thread::sleep_for(0.5s);
}
return 0;
}
$ ./heating-demo-v1
*** Round 1 ...
DEMO-LOGGER: sensorA
DEMO-STORE: setting sensorA = 40.1392
DEMO-LOGGER: sensorB
DEMO-STORE: setting sensorB = 4
DEMO-LOGGER: sensorC
DEMO-STORE: setting sensorC = 12597.1
...
Step 2: And D-Bus? ⟶ Interfaces¶
SensorReader
depends on concrete implementationsSee how we can log and store via D-Bus
Create Interfaces
Pull out
SensorReader
(/trainings/material/soup/cxx-code/heating-screenplay/sensor-reader.h
)Modify demo program (
/trainings/material/soup/cxx-code/heating-screenplay/heating-demo-v2.cpp
)
#pragma once
#include <sensor.h>
#include <vector>
#include <string>
// * reads a configured list of sensors
// * writes name/sensor-value pairs into a value store of some kind
// * logs messages as it goes
class SensorReader
{
public:
using NamedSensor = std::pair<std::string, Sensor*>;
using Sensors = std::vector<NamedSensor>;
// Aspects of a logger that I need: get rid of messages
class Logger
{
public:
virtual ~Logger() {}
virtual void log(const std::string& msg) = 0;
};
// Aspects of a value-store that I need: store double value under
// a name
class ValueStore
{
public:
virtual ~ValueStore() {}
virtual void set(const std::string& name, double temperature) = 0;
};
public:
SensorReader(
const Sensors& sensors,
Logger& logger,
ValueStore& store)
: _sensors(sensors),
_logger(logger),
_value_store(store) {}
void doit()
{
for (auto [name, sensor]: _sensors){
_logger.log(name);
double temperature = sensor->get_temperature();
_value_store.set(name, temperature);
}
}
private:
std::vector<NamedSensor> _sensors;
Logger& _logger;
ValueStore& _value_store;
};
#include "sensor-reader.h"
#include <sensor-const.h>
#include <sensor-random.h>
#include <chrono>
#include <thread>
#include <map>
#include <iostream>
using namespace std::chrono_literals;
class DemoLogger : public SensorReader::Logger
{
public:
void log(const std::string& msg) override
{
std::cerr << "DEMO-LOGGER: " << msg << std::endl;
}
};
class DemoValueStore : public SensorReader::ValueStore
{
public:
void set(const std::string& name, double value) override
{
std::cerr << "DEMO-STORE: setting " << name << " = " << value << std::endl;
}
private:
std::map<std::string/*name*/, double/*temperature*/> _store;
};
int main()
{
DemoLogger logger;
DemoValueStore store;
SensorReader::Sensors sensors{
{"sensorA", new RandomSensor(34.2, 41.3)},
{"sensorB", new ConstantSensor(4)},
{"sensorC", new RandomSensor(100, 200000)},
};
SensorReader rdr(
sensors,
logger,
store
);
for (auto round: {1,2,3,4,5}) {
std::cout << "*** Round " << round << " ..." << std::endl;
rdr.doit();
std::this_thread::sleep_for(0.5s);
}
return 0;
}
$ ./heating-demo-v2
*** Round 1 ...
DEMO-LOGGER: sensorA
DEMO-STORE: setting sensorA = 36.2895
DEMO-LOGGER: sensorB
DEMO-STORE: setting sensorB = 4
DEMO-LOGGER: sensorC
DEMO-STORE: setting sensorC = 158243
...
Step 3: Start D-Bus Implementation¶
Pull Demo Logger/Store Out Into Separate Files¶
Adapter: DBusLogger
¶
#pragma once
#include "sensor-reader.h"
#include <string>
#include <cassert>
class DBusLogger : public SensorReader::Logger
{
public:
void log(const std::string& msg) override
{
assert(!"Boss, we need a DBus consultant!!");
}
};
Adapter: DBusValueStore
¶
#pragma once
#include "sensor-reader.h"
#include <string>
#include <map>
class DBusValueStore : public SensorReader::ValueStore
{
public:
void set(const std::string& name, double value) override
{
assert(!"Boss, we need a DBus consultant!!");
}
};
Demo Program To Instantiate Either Demo Or DBus¶
#include "sensor-reader.h"
#include "logger-demo.h" // <--- pulled out to header
#include "valuestore-demo.h" // <--- pulled out to header
#include "logger-dbus.h" // <--- new header
#include "valuestore-dbus.h" // <--- new header
#include <sensor-const.h>
#include <sensor-random.h>
#include <chrono>
#include <thread>
#include <memory>
#include <iostream>
using namespace std::chrono_literals;
int main(int argc, char** argv)
{
std::unique_ptr<SensorReader::Logger> logger;
std::unique_ptr<SensorReader::ValueStore> store;
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " 'DEMO|DBUS'" << std::endl;
return 1;
}
std::string environ = argv[1];
if (environ == "DEMO") {
logger.reset(new DemoLogger);
store.reset(new DemoValueStore);
}
else if (environ == "DBUS") {
logger.reset(new DBusLogger);
store.reset(new DBusValueStore);
}
else {
std::cerr << "Usage: " << argv[0] << " 'DEMO|DBUS'" << std::endl;
return 1;
}
SensorReader::Sensors sensors{
{"sensorA", new RandomSensor(34.2, 41.3)},
{"sensorB", new ConstantSensor(4)},
{"sensorC", new RandomSensor(100, 200000)},
};
SensorReader rdr(
sensors,
*logger,
*store
);
for (auto round: {1,2,3,4,5}) {
std::cout << "*** Round " << round << " ..." << std::endl;
rdr.doit();
std::this_thread::sleep_for(0.5s);
}
return 0;
}
Stop Here, Need Help¶
$ ./heating-demo-v3 DBUS
*** Round 1 ...
heating-demo-v3: /home/jfasch/work/jfasch-home/trainings/material/soup/cxx-design-patterns/exercises/../code/heating/logger-dbus.h:14: virtual void DBusLogger::log(const std::string&): Assertion `!"Boss, we need a DBus consultant!!"' failed.
Aborted (core dumped)
Call for consultant to do the dirty work
In the meantime, focus on stabilizing core logic (there’s a leak somewhere)
Note
We did not modify SensorReader
in a while!!
Tests¶
#include <gtest/gtest.h>
#include "sensor-reader.h"
#include <sensor-const.h>
#include <map>
namespace {
struct MockLogger : public SensorReader::Logger
{
void log(const std::string& msg) override
{
lines_logged++;
}
int lines_logged = 0;
};
struct MockValueStore : public SensorReader::ValueStore
{
void set(const std::string& name, double value)
{
values[name] = value;
}
std::map<const std::string, double> values;
};
}
TEST(sensorreader_suite, basics)
{
MockLogger logger;
MockValueStore store;
SensorReader::Sensors sensors {
{"sensor1", new ConstantSensor(1) },
{"sensor2", new ConstantSensor(2) },
};
SensorReader rdr(sensors, logger, store);
rdr.doit();
ASSERT_FLOAT_EQ(store.values["sensor1"], 1);
ASSERT_FLOAT_EQ(store.values["sensor2"], 2);
}
$ ./heating-tests
Running main() from /home/jfasch/work/jfasch-home/googletest/googletest/src/gtest_main.cc
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from sensorreader_suite
[ RUN ] sensorreader_suite.basics
[ OK ] sensorreader_suite.basics (0 ms)
[----------] 1 test from sensorreader_suite (0 ms total)
[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (0 ms total)
[ PASSED ] 1 test.
$ valgrind ./heating-tests
==141320== Memcheck, a memory error detector
==141320== HEAP SUMMARY:
==141320== in use at exit: 32 bytes in 2 blocks
==141320== total heap usage: 204 allocs, 202 frees, 113,874 bytes allocated