Firmware: Dedicated Thread For “Operator Button” Event#

../../../../../../../_images/fsm-operator-button-thread.svg

Plan#

  • Move “operator button” IRQ handling to main() - much like we moved “sensor burning

  • FSM relieved from “operator button” IRQ artifacts completely

  • Public method FSM::operator_button_pressed()

  • Dedicated semaphore and thread in main()

  • FSM completely passive: no IRQ, no timer, no nothing

    • Anybody can call timer_tick(), sensor_burning(), operator_button_pressed()

    • ⟶ YAY UNIT TESTING!!!

main(): “Operator Button” Thread#

#include "sensor.h"
#include "switch.h"
#include "fsm.h"

#include <can-jf0815.h>

#include <why-timer.h>
#include <why-irq.h>
#include <why-time.h>
#include <why-thread.h>

#include <print>
#include <cstring>


int main()
{
    Why::init();

    Sensor sensor("/tmp/plant/temperature");
    Switch cooling_switch("/tmp/plant/cooling");
    Switch soft_alarm("/tmp/plant/soft-alarm");
    Switch hard_alarm("/tmp/plant/hard-alarm");
    std::string can_iface("my-can");
    const int can_id = 37;
    CAN_JF0815 can_controller(can_iface);
    Why::MessageQueue<FSM::State> state_change_queue;
    Why::Semaphore timer_tick_sem(0);
    Why::Timer interval_timer([&timer_tick_sem](){
        timer_tick_sem.give();
    });
    Why::Semaphore sensor_burning_sem(0);
    Why::Semaphore operator_button_sem(0);             // <-- signaled/given by "operator button" IRQ

    const long LOW_TEMP = 20;
    const long HIGH_TEMP = 500'000;
    const long SOFT_ALARM_TEMP = 1'000'000;
    const int SENSOR_BURNING_IRQ = 7;
    const int OPERATOR_BUTTON_IRQ = 42;

    std::println("* Thermostat lo: {}", LOW_TEMP);
    std::println("* Thermostat hi: {}", HIGH_TEMP);
    std::println("* Soft critical: {}", SOFT_ALARM_TEMP);
    std::println("* Cooling switch: {}", cooling_switch.path());
    std::println("* Soft alarm: {}", soft_alarm.path());
    std::println("* Hard alarm: {}", hard_alarm.path());
    std::println("* Sensor burning IRQ: {}", SENSOR_BURNING_IRQ);
    std::println("* Operator button IRQ: {}", OPERATOR_BUTTON_IRQ);

    FSM fsm(
        hard_alarm,
        SOFT_ALARM_TEMP, soft_alarm,
        state_change_queue,
        sensor, cooling_switch,
        LOW_TEMP, HIGH_TEMP
    );

    fsm.start();

    Why::Thread can_thread(
        [&can_controller, &state_change_queue](){
            while (true) {
                FSM::State state =
                    state_change_queue.get();

                uint32_t notify_state;
                switch (state) {
                    case FSM::State::INIT:
                        notify_state = 0;
                        break;
                    case FSM::State::RUNNING:
                        notify_state = 1;
                        break;
                    case FSM::State::OVERTEMP:
                        notify_state = 2;
                        break;
                    case FSM::State::SENSORBURNING:
                        notify_state = 3;
                        break;
                }

                CANFrame frame;
                frame.id = can_id;
                frame.dlc = sizeof(notify_state);
                memcpy(frame.data, &notify_state, sizeof(notify_state));

                can_controller.xmit(frame);
            }
        }
    );

    Why::Thread timer_tick_thread(
        [&fsm, &timer_tick_sem](){
            while (true) {
                timer_tick_sem.take();
                fsm.timer_tick();
            }
        });
    interval_timer.start(
        Why::TimeSpec(0,1), Why::TimeSpec(0, 1'000'000));

    Why::Thread sensor_burning_thread(
        [&fsm, &sensor_burning_sem](){
            while (true) {
                sensor_burning_sem.take();
                fsm.sensor_burning();
            }
        });
    Why::IRQ::connect(
        SENSOR_BURNING_IRQ,
        [&sensor_burning_sem](int irqnum) {
            sensor_burning_sem.give();
        }
    );

    Why::Thread operator_button_thread(
        [&fsm, &operator_button_sem](){
            while (true) {
                operator_button_sem.take();            // <-- wait until sensor burns
                fsm.operator_button_pressed();         // <-- drive FSM
            }
        });
    Why::IRQ::connect(                                 // <-- activate "operator button" IRQ
        OPERATOR_BUTTON_IRQ,
        [&operator_button_sem](int irqnum) {
            operator_button_sem.give();                // <-- signal/wake "operator button" thread
        }
    );

    Why::pause();
    return 0;
}

FSM: As Small As Possible#

#pragma once

#include "sensor.h"
#include "switch.h"

#include <why-spinlock.h>
#include <why-messagequeue.h>


class FSM
{
public:
    enum class State {
        INIT,
        RUNNING,
        OVERTEMP,
        SENSORBURNING,
    };

public:
    FSM(Switch& hard_alarm,
        long soft_critical_temp, Switch& soft_alarm,
        Why::MessageQueue<State>& state_change_queue,
        Sensor& sensor, Switch& cooling,
        long low_temp, long high_temp);

    void start();

    void timer_tick();
    void sensor_burning();
    void operator_button_pressed();                    // <-- external event

private:
    void _state_change(State state);

private:
    Why::Spinlock _lock;

    Switch& _hard_alarm;
    long _soft_critical_temp;
    Switch& _soft_alarm;
    Sensor& _sensor;
    Switch& _cooling;
    long _low_temp;
    long _high_temp;

    Why::MessageQueue<State>& _state_change_queue;

    State _state;
};
#include "fsm.h"

#include <why-irq.h>

#include <cassert>
#include <cstring>


FSM::FSM(
    Switch& hard_alarm,
    long soft_critical_temp, Switch& soft_alarm,
    Why::MessageQueue<State>& state_change_queue,
    Sensor& sensor, Switch& cooling,
    long low_temp, long high_temp)
: _hard_alarm(hard_alarm),
  _soft_critical_temp(soft_critical_temp),
  _soft_alarm(soft_alarm),
  _sensor(sensor), 
  _cooling(cooling), 
  _low_temp(low_temp), 
  _high_temp(high_temp), 
  _state_change_queue(state_change_queue),
  _state(State::INIT)
{}

void FSM::start()
{
    _lock.lock();
    switch (_state) {
        case State::INIT:
            _sensor.turn_on();
            _state_change(State::RUNNING);
            break;
        case State::RUNNING:
            assert(!"RUNNING");
            break;
        case State::OVERTEMP:
            assert(!"OVERTEMP");
            break;
        case State::SENSORBURNING:
            assert(!"SENSORBURNING");
            break;
    }
    _lock.unlock();
}

void FSM::timer_tick()
{
    _lock.lock();
    switch (_state) {
        case State::INIT:
            assert(!"INIT");
            break;
        case State::RUNNING: {
            double temp = _sensor.get_temperature();
            
            if (temp > _soft_critical_temp) {
                _cooling.set_state(true);
                _soft_alarm.set_state(true);
                _state_change(State::OVERTEMP);
            }
            else if (temp > _high_temp)
                _cooling.set_state(true);
            else if (temp < _low_temp)
                _cooling.set_state(false);
            break;
        }
        case State::OVERTEMP: {
            double temp = _sensor.get_temperature();
            
            if (temp > _high_temp)
                _cooling.set_state(true);
            else if (temp < _low_temp)
                _cooling.set_state(false);
            break;
        }
        case State::SENSORBURNING:
            break;
    }
    _lock.unlock();
}
    
void FSM::sensor_burning()
{
    _lock.lock();
    switch (_state) {
        case State::INIT:
            assert(!"INIT");
            break;
        case State::RUNNING:
            _sensor.turn_off();
            _cooling.set_state(false);
            _hard_alarm.set_state(true);
            _state_change(State::SENSORBURNING);
            break;
        case State::OVERTEMP:
            _sensor.turn_off();
            _cooling.set_state(false);
            _soft_alarm.set_state(false);
            _hard_alarm.set_state(true);
            _state_change(State::SENSORBURNING);
            break;
        case State::SENSORBURNING:
            break;
    }
    _lock.unlock();
}

void FSM::operator_button_pressed()
{
    _lock.lock();
    switch (_state) {
        case State::INIT:
            assert(!"INIT");
            break;
        case State::RUNNING:
            break;
        case State::OVERTEMP:
            _soft_alarm.set_state(false);
            _state_change(State::RUNNING);
            break;
        case State::SENSORBURNING:
            break;
    }
    _lock.unlock();
}

void FSM::_state_change(State state)
{
    auto written = 
        _state_change_queue.put_noblock(state);
    if (!written)
        std::println("state changes too fast?!");
    _state = state;
}