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