Return to Home Page

Observers

On the Pico, classes that respond to incoming IBUS messages extend BaseObserver.

BaseObserver is a base class:

namespace pico::ibus::observers {

    class BaseObserver {

    public:
        /// Called by the ObserverRegistry
        void dispatchPacket(
                std::shared_ptr<pico::logger::BaseLogger> logger,
                std::shared_ptr<pico::ibus::data::IbusPacket> iBusPacket
                );

        virtual std::string getTag() = 0;

    protected:
        virtual void onNewPacket(std::shared_ptr<pico::ibus::data::IbusPacket> iBusPacket) = 0;
        messages::PiToPicoMessage decodePiToPicoMessage(
                std::shared_ptr<pico::logger::BaseLogger> logger,
                pico::ibus::data::IbusPacket ibusPacket);

    private:
        /**
         *  Whether to log "Dispatching Packet" and "Dispatched Packet"
         *  before the observer does the match. To keep logs from being
         *  spammy, and observer should only enable this if it is at risk
         *  of being hung.
         */
        bool log_packetDispatchTrace = false;

    };

} // observers

This design allows every Observer to include its dependencies seperately and facilitates unit testing. It also consolidates the PiToPicoMessage decoding into one place.

ObserverRegistry and Factory relationship

Calls to Observers happen when the DmaManager receives a packet, and calls on the ObserverRegistry to dispatchMessageToAllObservers. DmaManager is constructed by the factory with a reference to an ObserverRegistry. Then, the factory constructs all the individual observers, re-using shared references when possible, and adds them all into a baseObservers list:

namespace pico::ibus::observerRegistry {
    class ObserverRegistry {
    
    private:
        std::shared_ptr<logger::BaseLogger> logger;
        std::vector<std::shared_ptr<pico::ibus::observers::BaseObserver>> observerList;

    public:
        ObserverRegistry(std::shared_ptr<pico::logger::BaseLogger> logger);
        void registerObserver(std::shared_ptr<pico::ibus::observers::BaseObserver> observer);
        void unregisterObserver(std::shared_ptr<pico::ibus::observers::BaseObserver> observer);

        void dispatchMessageToAllObservers(data::IbusPacket packet);

        void printRegisteredObserverTags();
    };
} // observerRegistry

Next, the ApplicationContainer, onCpu0Setup(), registers all the observers with the ObserverRegistry.

This allows a “de-knot” of dependencies in the Factory, and allows the DmaManager to not have to know about what observers it’ll be dispatching to. This design also allows dynamically adding and removing observers at run-time.

Sub-classes

The subclasses of BaseObserver are all located in here

A typical Observer (TelephonePressNoVideo)

This example shows how the the telephone button can be used (when the pico is configured to not have video support) to switch between video sources, and to power-cycle the pi.

namespace pico::ibus::observers {
    TelephonePressNoVideo::TelephonePressNoVideo(
            std::shared_ptr<logger::BaseLogger> baseLogger,
            std::shared_ptr<hardware::videoSwitch::VideoSwitch> videoSwitch,
            std::shared_ptr<pico::ibus::output::writer::ScreenPowerManager> screenPowerManager,
            std::shared_ptr<pico::ibus::output::writer::TestingOutputWriter> testingOutputWriter,
            std::shared_ptr<pico::hardware::pi4powerswitch::IPi4PowerSwitchManager> pi4PowerSwitchManager) {

        this->logger = baseLogger;
        this->videoSwitch = videoSwitch;
        this->screenPowerManager = screenPowerManager;
        this->testingOutputWriter = testingOutputWriter;
        this->pi4PowerSwitchManager = pi4PowerSwitchManager;
    }

    void TelephonePressNoVideo::restartRpi() {
        pi4PowerSwitchManager->setPower(false);
        sleep_ms(500);
        pi4PowerSwitchManager->setPower(true);
    }

    void TelephonePressNoVideo::swapToNextVideoSource() {


        hardware::videoSwitch::VideoSource previous = this->videoSwitch->getPreviousVideoSource();
        hardware::videoSwitch::VideoSource next;
        switch (previous) {
            case hardware::videoSwitch::RVC:
                next = hardware::videoSwitch::UPSTREAM;
                break;
            case hardware::videoSwitch::UPSTREAM:
            case hardware::videoSwitch::PICO:
                next = hardware::videoSwitch::PI;
                break;
            case hardware::videoSwitch::PI:
                next = hardware::videoSwitch::RVC;
                break;
        }
        this->videoSwitch->switchTo(next);
    }

    void TelephonePressNoVideo::onTelephonePressed() {
        swapToNextVideoSource();
    }

    void TelephonePressNoVideo::onTelephoneLongPressed() {
        restartRpi();
    }

    void TelephonePressNoVideo::onNewPacket(std::shared_ptr<pico::ibus::data::IbusPacket> iBusPacket) {
        if (iBusPacket->getSourceDevice() == data::IbusDeviceEnum::BOARDMONITOR_BUTTONS
            && iBusPacket->getDestinationDevice() == data::IbusDeviceEnum::BROADCAST) {

            if (iBusPacket->getDataLength() >= 2) {

                if (iBusPacket->getRawPacket()[data::IbusPacket::DATA_START + 0] == 0x48) {
                    uint8_t command = iBusPacket->getRawPacket()[data::IbusPacket::DATA_START + 1];

                    if (command == 0x08) {
                        onTelephonePressed();
                        return;
                    }


                    if (command == 0x88) {
                        return;
                    }

                    if (command == 0x48) {
                        onTelephoneLongPressed();
                        return;
                    }
                }
            }
        }
    }


} // observers
Return to Top