<!-- language-all: lang-c -->
There is no need for either effectors or sensors to know about each other or to be organized in registries. There is no need for global state either.
Use dumbed down low level dependency injection. Connect sensors and logic/effectors upon initialization and update each in whatever module/function is convenient.
// HAL
struct TemperatureSensor {
float currentTemperatureCelcius;
int port;
}
void updateTemperature(TemperatureSensor * sensor) {
sensor->currentTemperatureCelcius = readTemperature(sensor->port);
}
struct HeaterEffector {
float power;
int port;
}
void updatePower(HeaterEffector * effector) {
writePower(effector->port, effector->power);
}
// Logic
struct PID {
float * input;
float * output;
float target;
}
void adjustPID(PID * pid) {
// TODO: PID
*pid->output = pid->target - *pid->input;
}
void main(int argc, char **argv) {
// The structures are independent, can be allocated however convenient, no need for registries
TemperatureSensor temperatureSensor = {0, atoi(argv[1])};
HeaterEffector heaterEffector = {0, atoi(argv[2])};
PID pid = {&temperatureSensor.currentTemperatureCelcius, &heaterEffector->power, atof(argv[3])};
for(;;) {
// The calls below are completely independent and can be wherever
updateTemperature(&temperatureSensor);
adjustPID(&pid);
updatePower(&heater);
}
}
True IoC implies injection of behavior, but for a simple program, injection of state would be enough.
# Disclaimer
The supplied example will break in a multi-threaded environment if floats are misaligned or CPU architecture tears float-sized reads.