M module-apps/Application.cpp => module-apps/Application.cpp +3 -3
@@ 135,12 135,12 @@ namespace app
window->updateSignalStrength();
window->updateNetworkAccessTechnology();
- auto message = std::make_shared<sgui::DrawMessage>(window->buildDrawList(), mode);
+ auto message = std::make_shared<service::gui::DrawMessage>(window->buildDrawList(), mode);
if (shutdownInProgress) {
- message->setCommandType(sgui::DrawMessage::Type::SHUTDOWN);
+ message->setCommandType(service::gui::DrawMessage::Type::SHUTDOWN);
}
else if (suspendInProgress) {
- message->setCommandType(sgui::DrawMessage::Type::SUSPEND);
+ message->setCommandType(service::gui::DrawMessage::Type::SUSPEND);
}
sys::Bus::SendUnicast(message, service::name::gui, this);
}
M module-apps/application-desktop/windows/PowerOffWindow.cpp => module-apps/application-desktop/windows/PowerOffWindow.cpp +18 -1
@@ 4,6 4,7 @@
#include "InputEvent.hpp"
#include "gui/widgets/BottomBar.hpp"
#include "gui/widgets/TopBar.hpp"
+#include "GuiTimer.hpp"
#include "log/log.hpp"
// module-utils
@@ 141,7 142,7 @@ namespace gui
infoLabel->setVisible(false);
application->refreshWindow(RefreshModes::GUI_REFRESH_DEEP);
-
+ scheduleSystemShutdown();
return true;
};
@@ 159,6 160,22 @@ namespace gui
};
}
+ // Temporary solution for shutting down the system.
+ // The former solution removed from service-gui during its refactor.
+ // To be reimplemented in SystemManager in upcoming commits.
+ void PowerOffWindow::scheduleSystemShutdown()
+ {
+ constexpr auto SystemShutdownDelayInMs = 500;
+ auto powerOffTimer = std::make_unique<app::GuiTimer>("PowerOffTimer", application);
+ powerOffTimer->setInterval(SystemShutdownDelayInMs);
+ timerCallback = [this](Item &, Timer &timer) {
+ detachTimer(timer);
+ sys::SystemManager::CloseSystem(application);
+ return true;
+ };
+ application->connect(std::move(powerOffTimer), this);
+ }
+
void PowerOffWindow::destroyInterface()
{
erase();
M module-apps/application-desktop/windows/PowerOffWindow.hpp => module-apps/application-desktop/windows/PowerOffWindow.hpp +2 -1
@@ 13,7 13,6 @@ namespace gui
{
class PowerOffWindow : public AppWindow
{
-
enum class State
{
PowerDown,
@@ 29,6 28,8 @@ namespace gui
gui::Image *powerDownImage = nullptr;
State state = State::Return;
+ void scheduleSystemShutdown();
+
public:
PowerOffWindow(app::Application *app);
void onBeforeShow(ShowMode mode, SwitchData *data) override;
M module-gui/gui/core/Context.hpp => module-gui/gui/core/Context.hpp +13 -9
@@ 51,26 51,30 @@ namespace gui
/**
* @brief returns pointer to context's data;
*/
+ inline const uint8_t *getData() const
+ {
+ return data;
+ }
inline uint8_t *getData()
{
return data;
- };
- inline int16_t getX()
+ }
+ inline int16_t getX() const
{
return x;
- };
- inline int16_t getY()
+ }
+ inline int16_t getY() const
{
return y;
- };
- inline uint16_t getW()
+ }
+ inline uint16_t getW() const
{
return w;
- };
- inline uint16_t getH()
+ }
+ inline uint16_t getH() const
{
return h;
- };
+ }
inline bool addressInData(const uint8_t *ptr) const
{
M module-gui/gui/core/RawFont.cpp => module-gui/gui/core/RawFont.cpp +1 -1
@@ 306,7 306,7 @@ namespace gui
commandRect->penWidth = unsupported->xoffset;
auto renderCtx = std::make_unique<Context>(unsupported->width, unsupported->height);
- std::list<Command> commands;
+ std::list<std::unique_ptr<gui::DrawCommand>> commands;
commands.emplace_back(std::move(commandRect));
Renderer().render(renderCtx.get(), commands);
M module-gui/gui/core/Renderer.cpp => module-gui/gui/core/Renderer.cpp +1 -1
@@ 288,7 288,7 @@ namespace gui
delete drawCtx;
}
- void Renderer::render(Context *ctx, std::list<Command> &commands)
+ void Renderer::render(Context *ctx, std::list<std::unique_ptr<DrawCommand>> &commands)
{
if (ctx == nullptr) {
return;
M module-gui/gui/core/Renderer.hpp => module-gui/gui/core/Renderer.hpp +1 -1
@@ 50,7 50,7 @@ namespace gui
public:
virtual ~Renderer() = default;
- void render(Context *ctx, std::list<Command> &commands);
+ void render(Context *ctx, std::list<std::unique_ptr<DrawCommand>> &commands);
};
} /* namespace gui */
M module-os/RTOSWrapper/cqueue.cpp => module-os/RTOSWrapper/cqueue.cpp +8 -0
@@ 62,6 62,14 @@ Queue::~Queue()
vQueueDelete(handle);
}
+bool Queue::Overwrite(void *item)
+{
+ BaseType_t success;
+
+ success = xQueueOverwrite(handle, item);
+
+ return success == pdTRUE ? true : false;
+}
bool Queue::Enqueue(void *item)
{
M module-os/RTOSWrapper/include/queue.hpp => module-os/RTOSWrapper/include/queue.hpp +8 -0
@@ 136,6 136,14 @@ class Queue {
virtual ~Queue();
/**
+ * Add an item to the queue, or overwrites the existing one.
+ * Works only for queues 1-element long.
+ * @param item The item to be added.
+ * @return true if the item was added, false otherwise.
+ */
+ bool Overwrite(void *item);
+
+ /**
* Add an item to the back of the queue.
*
* @param item The item you are adding.
M module-services/service-appmgr/model/ApplicationManager.cpp => module-services/service-appmgr/model/ApplicationManager.cpp +2 -5
@@ 6,7 6,6 @@
#include <module-apps/messages/AppMessage.hpp>
#include <Common.hpp>
-#include <Common/Common.hpp>
#include <Service/Bus.hpp>
#include <Service/Message.hpp>
#include <Service/Timer.hpp>
@@ 16,10 15,8 @@
#include <i18n/i18n.hpp>
#include <log/log.hpp>
#include <service-appmgr/messages/Message.hpp>
-#include <service-db/DBServiceAPI.hpp>
#include <service-evtmgr/EventManager.hpp>
#include <service-gui/ServiceGUI.hpp>
-#include <service-eink/Common.hpp>
#include <service-eink/ServiceEink.hpp>
#include <service-gui/Common.hpp>
@@ 135,12 132,12 @@ namespace app::manager
void ApplicationManager::startSystemServices()
{
if (bool ret = sys::SystemManager::CreateService(
- std::make_shared<sgui::ServiceGUI>(service::name::gui, GetName(), 480, 600), this);
+ std::make_shared<service::gui::ServiceGUI>(service::name::gui, GetName()), this);
!ret) {
LOG_ERROR("Failed to initialize GUI service");
}
if (bool ret = sys::SystemManager::CreateService(
- std::make_shared<eink::ServiceEink>(service::name::eink, GetName()), this);
+ std::make_shared<service::eink::ServiceEink>(service::name::eink, GetName()), this);
!ret) {
LOG_ERROR("Failed to initialize EInk service");
}
M module-services/service-eink/CMakeLists.txt => module-services/service-eink/CMakeLists.txt +2 -1
@@ 14,8 14,9 @@ message( "EINK BOARD PATH: ${CMAKE_CURRENT_LIST_DIR}/${EINK_BOARD_PATH}" )
set(SOURCES
ServiceEink.cpp
- EinkScreen.cpp
+ EinkDisplay.cpp
messages/ImageMessage.cpp
+ messages/PrepareDisplayRequest.cpp
)
add_library(${PROJECT_NAME} STATIC ${SOURCES})
M module-services/service-eink/Common.hpp => module-services/service-eink/Common.hpp +12 -3
@@ 3,7 3,16 @@
#pragma once
-namespace service::name
+namespace service
{
- constexpr char eink[] = "ServiceEink";
-};
+ namespace name
+ {
+ constexpr inline auto eink = "ServiceEink";
+ } // namespace name
+
+ namespace eink
+ {
+ constexpr inline auto DefaultScreenWidth = 480;
+ constexpr inline auto DefaultScreenHeight = 600;
+ } // namespace eink
+} // namespace service
R module-services/service-eink/EinkScreen.cpp => module-services/service-eink/EinkDisplay.cpp +45 -71
@@ 1,7 1,7 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-#include "EinkScreen.hpp"
+#include "EinkDisplay.hpp"
#include <gui/core/Color.hpp>
#include <gsl/gsl_util>
@@ 9,7 9,7 @@
#include <cstdio>
#include <cstring>
-namespace eink
+namespace service::eink
{
namespace
{
@@ 19,6 19,9 @@ namespace eink
constexpr auto LUTCSize = 64;
constexpr auto LUTRSize = 256; ///< Needed due to \ref EINK_LUTS_FILE_PATH structure
constexpr auto LUTSTotalSize = LUTDSize + LUTCSize + LUTRSize;
+ constexpr auto LUTVersionInterval = 3;
+ constexpr auto LUTSubcritical = 12;
+ constexpr auto LUTCritical = 13;
EinkWaveFormSettings_t createDefaultWaveFormSettings(EinkWaveforms_e waveformMode)
{
@@ 31,102 34,69 @@ namespace eink
settings.LUTDSize = 0;
return settings;
}
-
- std::unique_ptr<std::uint8_t[]> allocateScreenBuffer(gui::Size screenSize)
- {
- return std::make_unique<std::uint8_t[]>(screenSize.width * screenSize.height);
- }
} // namespace
- EinkScreen::EinkScreen(gui::Size screenSize)
- : size{screenSize}, screenBuffer{allocateScreenBuffer(screenSize)},
- waveformSettings{createDefaultWaveFormSettings(EinkWaveformGC16)},
+ EinkDisplay::EinkDisplay(::gui::Size screenSize)
+ : size{screenSize}, waveformSettings{createDefaultWaveFormSettings(EinkWaveformGC16)},
displayMode{EinkDisplayColorMode_e::EinkDisplayColorModeStandard}
{}
- EinkScreen::~EinkScreen() noexcept
+ EinkDisplay::~EinkDisplay() noexcept
{
delete[] waveformSettings.LUTCData;
delete[] waveformSettings.LUTDData;
}
- EinkStatus_e EinkScreen::resetAndInit()
+ EinkStatus_e EinkDisplay::resetAndInit()
{
return EinkResetAndInitialize();
}
- void EinkScreen::dither()
+ void EinkDisplay::dither()
{
EinkDitherDisplay();
}
- void EinkScreen::powerOn()
+ void EinkDisplay::powerOn()
{
EinkPowerOn();
}
- void EinkScreen::powerOff()
+ void EinkDisplay::powerOff()
{
EinkPowerOff();
}
- void EinkScreen::shutdown()
+ void EinkDisplay::shutdown()
{
EinkPowerDown();
}
- void EinkScreen::setScreenBuffer(const std::uint8_t *buffer, std::uint32_t bufferSize)
- {
- std::memcpy(screenBuffer.get(), buffer, bufferSize);
- }
-
- void EinkScreen::setScreenBuffer(std::uint8_t value, std::uint32_t bufferSize)
+ EinkStatus_e EinkDisplay::update(std::uint8_t *displayBuffer)
{
- std::memset(screenBuffer.get(), value, bufferSize);
+ return EinkUpdateFrame(pointTopLeft.x,
+ pointTopLeft.y,
+ size.width,
+ size.height,
+ displayBuffer,
+ getCurrentBitsPerPixelFormat(),
+ displayMode);
}
- EinkStatus_e EinkScreen::update()
+ EinkBpp_e EinkDisplay::getCurrentBitsPerPixelFormat() const noexcept
{
- return EinkUpdateFrame(
- pointTopLeft.x, pointTopLeft.y, size.width, size.height, screenBuffer.get(), Eink4Bpp, displayMode);
- }
-
- EinkStatus_e EinkScreen::refresh(EinkDisplayTimingsMode_e refreshMode)
- {
- return EinkRefreshImage(pointTopLeft.x, pointTopLeft.y, size.width, size.height, refreshMode);
- }
-
- bool EinkScreen::deepClear(std::int32_t temperature)
- {
- const auto waveformMode = waveformSettings.mode;
-
- powerOn();
- changeWaveform(EinkWaveforms_e::EinkWaveformA2, temperature);
-
- fillScreen(gui::Color::White);
- for (auto i = 0; i < 2; ++i) {
- fillScreen(gui::Color::Black);
- fillScreen(gui::Color::White);
+ if (waveformSettings.mode == EinkWaveformDU2) {
+ return Eink1Bpp;
}
-
- changeWaveform(waveformMode, temperature);
- powerOff();
- return true;
+ return Eink4Bpp;
}
- void EinkScreen::fillScreen(std::uint8_t colorIntensity)
+ EinkStatus_e EinkDisplay::refresh(EinkDisplayTimingsMode_e refreshMode)
{
- const auto screenBufferSize = size.width * size.height;
- setScreenBuffer(colorIntensity, screenBufferSize);
- if (const auto status = update(); status != EinkOK) {
- LOG_FATAL("Failed to update frame");
- }
- if (const auto status = refresh(EinkDisplayTimingsFastRefreshMode); status != EinkOK) {
- LOG_FATAL("Failed to refresh frame");
- }
+ return EinkRefreshImage(pointTopLeft.x, pointTopLeft.y, size.width, size.height, refreshMode);
}
- bool EinkScreen::changeWaveform(EinkWaveforms_e mode, std::int32_t temperature)
+ bool EinkDisplay::changeWaveform(EinkWaveforms_e mode, std::int32_t temperature)
{
if (temperature == waveformSettings.temperature && mode == waveformSettings.mode) {
return EinkOK;
@@ 161,18 131,18 @@ namespace eink
return true;
}
- unsigned int EinkScreen::calculateWaveFormSegment(std::int32_t temperature) const
+ unsigned int EinkDisplay::calculateWaveFormSegment(std::int32_t temperature) const
{
if (temperature < 38) {
- return temperature / 3;
+ return temperature / LUTVersionInterval;
}
if (temperature < 43) {
- return 12;
+ return LUTSubcritical;
}
- return 13;
+ return LUTCritical;
}
- unsigned int EinkScreen::calculateWaveFormOffset(EinkWaveforms_e mode, unsigned int segment) const
+ unsigned int EinkDisplay::calculateWaveFormOffset(EinkWaveforms_e mode, unsigned int segment) const
{
switch (mode) {
case EinkWaveformINIT:
@@ 184,27 154,31 @@ namespace eink
case EinkWaveformGLD16:
return LUTSTotalSize * (42 + segment);
case EinkWaveformGC16:
- [[fallthrough]];
- default:
return LUTSTotalSize * (56 + segment);
}
+ throw std::invalid_argument{"Invalid waveform mode."};
}
- void EinkScreen::resetWaveFormSettings()
+ void EinkDisplay::resetWaveFormSettings()
{
delete[] waveformSettings.LUTDData;
waveformSettings.LUTDSize = 0;
- waveformSettings.LUTDData = new uint8_t[LUTDSize + 1];
+ waveformSettings.LUTDData = new std::uint8_t[LUTDSize + 1];
waveformSettings.LUTDData[0] = EinkLUTD;
delete[] waveformSettings.LUTCData;
waveformSettings.LUTCSize = LUTCSize;
- waveformSettings.LUTCData = new uint8_t[LUTCSize + 1];
+ waveformSettings.LUTCData = new std::uint8_t[LUTCSize + 1];
waveformSettings.LUTCData[0] = EinkLUTC;
}
- void EinkScreen::setDisplayMode(EinkDisplayColorMode_e mode) noexcept
+ void EinkDisplay::setMode(EinkDisplayColorMode_e mode) noexcept
{
displayMode = mode;
}
-} // namespace eink
+
+ ::gui::Size EinkDisplay::getSize() const noexcept
+ {
+ return size;
+ }
+} // namespace service::eink
R module-services/service-eink/EinkScreen.hpp => module-services/service-eink/EinkDisplay.hpp +18 -16
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ 6,44 6,46 @@
#include <gui/Common.hpp>
#include <EinkIncludes.hpp>
+#include "Common.hpp"
#include <cstdint>
#include <memory>
-namespace eink
+namespace service::eink
{
- class EinkScreen
+ /**
+ * Specifies the Eink display.
+ * Responsible for handling low-level Eink display operations, e.g. switching power modes, updating, refreshing,
+ * etc.
+ */
+ class EinkDisplay
{
public:
- explicit EinkScreen(gui::Size screenSize = {480, 600});
- ~EinkScreen() noexcept;
+ explicit EinkDisplay(::gui::Size screenSize = {DefaultScreenWidth, DefaultScreenHeight});
+ ~EinkDisplay() noexcept;
EinkStatus_e resetAndInit();
- EinkStatus_e update();
+ EinkStatus_e update(std::uint8_t *displayBuffer);
EinkStatus_e refresh(EinkDisplayTimingsMode_e refreshMode);
void dither();
void powerOn();
void powerOff();
void shutdown();
- bool deepClear(std::int32_t temperature);
bool changeWaveform(EinkWaveforms_e mode, std::int32_t temperature);
+ void setMode(EinkDisplayColorMode_e mode) noexcept;
- void setScreenBuffer(const std::uint8_t *buffer, std::uint32_t bufferSize);
- void setDisplayMode(EinkDisplayColorMode_e mode) noexcept;
+ ::gui::Size getSize() const noexcept;
private:
- void fillScreen(std::uint8_t colorIntensity);
- void setScreenBuffer(std::uint8_t value, std::uint32_t bufferSize);
unsigned int calculateWaveFormSegment(std::int32_t temperature) const;
unsigned int calculateWaveFormOffset(EinkWaveforms_e mode, unsigned int segment) const;
void resetWaveFormSettings();
+ EinkBpp_e getCurrentBitsPerPixelFormat() const noexcept;
- static constexpr gui::Point pointTopLeft{0, 0};
-
- const gui::Size size;
- std::unique_ptr<std::uint8_t[]> screenBuffer;
+ static constexpr ::gui::Point pointTopLeft{0, 0};
+ const ::gui::Size size;
EinkWaveFormSettings_t waveformSettings;
EinkDisplayColorMode_e displayMode;
};
-} // namespace eink
+} // namespace service::eink
M module-services/service-eink/ServiceEink.cpp => module-services/service-eink/ServiceEink.cpp +61 -84
@@ 3,11 3,9 @@
#include "ServiceEink.hpp"
#include "messages/EinkModeMessage.hpp"
-#include "messages/EinkDMATransfer.hpp"
-#include "messages/StateRequest.hpp"
-#include "messages/TemperatureUpdate.hpp"
+#include "messages/PrepareDisplayRequest.hpp"
#include <service-gui/Common.hpp>
-#include <service-gui/messages/GUIDisplayReady.hpp>
+#include <service-gui/messages/EinkReady.hpp>
#include <time/ScopedTime.hpp>
#include <log/log.hpp>
@@ 18,7 16,7 @@
#include <cstring>
#include <memory>
-namespace eink
+namespace service::eink
{
namespace
{
@@ 26,55 24,48 @@ namespace eink
} // namespace
ServiceEink::ServiceEink(const std::string &name, std::string parent)
- : sys::Service(name, parent, ServceEinkStackDepth), selfRefereshTriggerCount{0},
- temperatureMeasurementTriggerCount{0}, powerOffTriggerCount{0},
- powerOffTimer("PwrOffTimer", this, 3000, sys::Timer::Type::SingleShot)
+ : sys::Service(name, parent, ServceEinkStackDepth), currentState{State::Running}
{
- connect(typeid(service::eink::EinkModeMessage),
+ connect(typeid(EinkModeMessage),
[this](sys::Message *message) -> sys::MessagePointer { return handleEinkModeChangedMessage(message); });
- connect(typeid(service::eink::EinkDMATransfer),
- [this](sys::Message *request) -> sys::MessagePointer { return handleEinkDMATransfer(request); });
-
- connect(typeid(service::eink::ImageMessage),
+ connect(typeid(ImageMessage),
[this](sys::Message *request) -> sys::MessagePointer { return handleImageMessage(request); });
- connect(typeid(service::eink::StateRequest),
- [this](sys::Message *request) -> sys::MessagePointer { return handleStateRequest(request); });
-
- connect(typeid(service::eink::TemperatureUpdate),
- [this](sys::Message *request) -> sys::MessagePointer { return handleTemperatureUpdate(request); });
+ connect(typeid(PrepareDisplayRequest),
+ [this](sys::Message *request) -> sys::MessagePointer { return handlePrepareRequest(request); });
}
- sys::MessagePointer ServiceEink::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)
+ sys::MessagePointer ServiceEink::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *response)
{
return std::make_shared<sys::ResponseMessage>();
}
sys::ReturnCodes ServiceEink::InitHandler()
{
- LOG_INFO("[ServiceEink] Initializing");
- if (const auto status = screen.resetAndInit(); status != EinkOK) {
- LOG_FATAL("Error: Could not initialize Eink display!\n");
+ LOG_INFO("Initializing");
+ if (const auto status = display.resetAndInit(); status != EinkOK) {
+ LOG_FATAL("Error: Could not initialize Eink display!");
return sys::ReturnCodes::Failure;
}
- screen.powerOn();
- auto msg = std::make_shared<service::gui::GUIDisplayReady>(suspendInProgress, shutdownInProgress);
+ display.powerOn();
+
+ auto msg = std::make_shared<service::gui::EinkReady>(display.getSize());
sys::Bus::SendUnicast(msg, service::name::gui, this);
+
return sys::ReturnCodes::Success;
}
sys::ReturnCodes ServiceEink::DeinitHandler()
{
- screen.shutdown();
+ display.shutdown();
return sys::ReturnCodes::Success;
}
sys::ReturnCodes ServiceEink::SwitchPowerModeHandler(const sys::ServicePowerMode mode)
{
- LOG_FATAL("[ServiceEink] PowerModeHandler: %s", c_str(mode));
-
+ LOG_INFO("PowerModeHandler: %s", c_str(mode));
switch (mode) {
case sys::ServicePowerMode::Active:
enterActiveMode();
@@ 90,21 81,19 @@ namespace eink
void ServiceEink::enterActiveMode()
{
- suspended = false;
+ setState(State::Running);
- if (const auto status = screen.resetAndInit(); status != EinkOK) {
- LOG_FATAL("Error: Could not initialize Eink display!\n");
+ if (const auto status = display.resetAndInit(); status != EinkOK) {
+ LOG_FATAL("Error: Could not initialize Eink display!");
}
- screen.powerOn();
- screen.powerOff();
+ display.powerOn();
+ display.powerOff();
}
void ServiceEink::suspend()
{
- suspended = true;
-
- powerOffTimer.stop();
- screen.shutdown();
+ setState(State::Suspended);
+ display.shutdown();
}
sys::MessagePointer ServiceEink::handleEinkModeChangedMessage(sys::Message *message)
@@ 113,79 102,67 @@ namespace eink
const auto displayMode = msg->getMode() == service::eink::EinkModeMessage::Mode::Normal
? EinkDisplayColorMode_e::EinkDisplayColorModeStandard
: EinkDisplayColorMode_e::EinkDisplayColorModeInverted;
- screen.setDisplayMode(displayMode);
+ display.setMode(displayMode);
return sys::MessageNone{};
}
- sys::MessagePointer ServiceEink::handleEinkDMATransfer(sys::Message *message)
+ sys::MessagePointer ServiceEink::handleImageMessage(sys::Message *request)
{
- utils::time::Scoped measurement("EinkDMATransfer");
-
- if (suspended) {
- if (suspendInProgress) {
- LOG_ERROR("drawing before suspend failed");
- suspendInProgress = false;
- }
- LOG_INFO("[ServiceEink] Received image while suspended, ignoring");
+ const auto message = static_cast<service::eink::ImageMessage *>(request);
+ if (isInState(State::Suspended)) {
+ LOG_WARN("Received image while suspended, ignoring");
return sys::MessageNone{};
}
+ utils::time::Scoped measurement("ImageMessage");
- screen.powerOn();
- if (const auto temperature = EinkGetTemperatureInternal(); deepRefresh) {
- screen.changeWaveform(EinkWaveforms_e::EinkWaveformGC16, temperature);
- screen.dither();
- }
- else {
- screen.changeWaveform(EinkWaveforms_e::EinkWaveformDU2, temperature);
- }
+ updateDisplay(message->getData(), message->getRefreshMode());
+ return std::make_shared<service::eink::ImageDisplayedNotification>(message->getContextId());
+ }
+
+ void ServiceEink::updateDisplay(std::uint8_t *frameBuffer, ::gui::RefreshModes refreshMode)
+ {
+ prepareDisplay(refreshMode);
- if (const auto status = screen.update(); status != EinkOK) {
+ if (const auto status = display.update(frameBuffer); status != EinkOK) {
LOG_FATAL("Failed to update frame");
}
+
+ const auto isDeepRefresh = refreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP;
if (const auto status =
- screen.refresh(deepRefresh ? EinkDisplayTimingsDeepCleanMode : EinkDisplayTimingsFastRefreshMode);
+ display.refresh(isDeepRefresh ? EinkDisplayTimingsDeepCleanMode : EinkDisplayTimingsFastRefreshMode);
status != EinkOK) {
LOG_FATAL("Failed to refresh frame");
}
-
- powerOffTimer.reload();
-
- auto msg = std::make_shared<service::gui::GUIDisplayReady>(suspendInProgress, shutdownInProgress);
- suspendInProgress = false;
- shutdownInProgress = false;
- sys::Bus::SendUnicast(msg, service::name::gui, this);
-
- return sys::MessageNone{};
}
- sys::MessagePointer ServiceEink::handleImageMessage(sys::Message *request)
+ void ServiceEink::prepareDisplay(::gui::RefreshModes refreshMode)
{
- auto message = static_cast<service::eink::ImageMessage *>(request);
+ display.powerOn();
- powerOffTimer.stop();
- screen.setScreenBuffer(message->getData(), message->getSize());
- deepRefresh = message->getDeepRefresh();
-
- shutdownInProgress = message->getShutdown();
- if (shutdownInProgress) {
- LOG_DEBUG("Shutdown In Progress");
+ const auto isDeepRefresh = refreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP;
+ if (const auto temperature = EinkGetTemperatureInternal(); isDeepRefresh) {
+ display.changeWaveform(EinkWaveforms_e::EinkWaveformGC16, temperature);
+ display.dither();
}
- suspendInProgress = message->getSuspend();
- if (suspendInProgress) {
- LOG_DEBUG("Suspend In Progress");
+ else {
+ display.changeWaveform(EinkWaveforms_e::EinkWaveformDU2, temperature);
}
+ }
- sys::Bus::SendUnicast(std::make_shared<service::eink::EinkDMATransfer>(), GetName(), this);
- return std::make_shared<sys::ResponseMessage>();
+ sys::MessagePointer ServiceEink::handlePrepareRequest(sys::Message *message)
+ {
+ const auto waveformUpdateMsg = static_cast<service::eink::PrepareDisplayRequest *>(message);
+ prepareDisplay(waveformUpdateMsg->getRefreshMode());
+ return sys::MessageNone{};
}
- sys::MessagePointer ServiceEink::handleStateRequest(sys::Message *)
+ void ServiceEink::setState(State state) noexcept
{
- return std::make_shared<service::gui::GUIDisplayReady>(suspendInProgress, shutdownInProgress);
+ currentState = state;
}
- sys::MessagePointer ServiceEink::handleTemperatureUpdate(sys::Message *)
+ bool ServiceEink::isInState(State state) const noexcept
{
- return nullptr;
+ return currentState == state;
}
-} // namespace eink
+} // namespace service::eink
M module-services/service-eink/ServiceEink.hpp => module-services/service-eink/ServiceEink.hpp +26 -38
@@ 3,64 3,52 @@
#pragma once
-#include <gui/Common.hpp>
-#include <EinkIncludes.hpp>
#include <Service/Common.hpp>
#include <Service/Message.hpp>
#include <Service/Service.hpp>
-#include <Service/Timer.hpp>
+#include "EinkDisplay.hpp"
+
+#include <chrono>
#include <cstdint>
#include <string>
-#include "EinkScreen.hpp"
-
-namespace eink
+namespace service::eink
{
class ServiceEink : public sys::Service
{
- protected:
- EinkScreen screen;
-
- // counts timer triggers from last self refresh
- uint32_t selfRefereshTriggerCount;
- // counts timer events from last temperature measurement
- uint32_t temperatureMeasurementTriggerCount;
- // counts trigger counts from last action that required eink to be powered on
- uint32_t powerOffTriggerCount;
-
- // number of timer triggers required to execute self refresh handler
- const uint32_t selfRefereshTriggerValue = 60;
- // number of timer triggers required to execute temperature measurement handler
- const uint32_t temperatureMeasurementTriggerValue = 5 * 60;
- // number of timer triggers from last action requiring power on eink to power down eink.
- const uint32_t powerOffTriggerValue = 3;
-
- bool suspended = false;
-
- bool suspendInProgress = false;
- bool shutdownInProgress = false;
- bool deepRefresh = false;
-
- sys::ms powerOffTime = 3000;
- sys::Timer powerOffTimer;
-
public:
explicit ServiceEink(const std::string &name, std::string parent = {});
- sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) override;
+ sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *response) override;
sys::ReturnCodes InitHandler() override;
sys::ReturnCodes DeinitHandler() override;
- sys::ReturnCodes SwitchPowerModeHandler(const sys::ServicePowerMode mode) override final;
+ sys::ReturnCodes SwitchPowerModeHandler(const sys::ServicePowerMode mode) override;
private:
+ enum class State
+ {
+ Running,
+ Suspended
+ };
+ void setState(State state) noexcept;
+ bool isInState(State state) const noexcept;
+
void enterActiveMode();
void suspend();
+ void updateDisplay(std::uint8_t *frameBuffer, ::gui::RefreshModes refreshMode);
+ void prepareDisplay(::gui::RefreshModes refreshMode);
sys::MessagePointer handleEinkModeChangedMessage(sys::Message *message);
- sys::MessagePointer handleEinkDMATransfer(sys::Message *message);
sys::MessagePointer handleImageMessage(sys::Message *message);
- sys::MessagePointer handleStateRequest(sys::Message *messge);
- sys::MessagePointer handleTemperatureUpdate(sys::Message *);
+ sys::MessagePointer handlePrepareRequest(sys::Message *message);
+
+ EinkDisplay display;
+ State currentState;
+
+ /*
+ * PowerOffTimer to be implemented when needed.
+ * It should power off the display when not used for 3000ms.
+ */
};
-} // namespace eink
+} // namespace service::eink
D module-services/service-eink/messages/EinkDMATransfer.hpp => module-services/service-eink/messages/EinkDMATransfer.hpp +0 -12
@@ 1,12 0,0 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#pragma once
-
-#include "EinkMessage.hpp"
-
-namespace service::eink
-{
- class EinkDMATransfer : public service::eink::EinkMessage
- {};
-}; // namespace service::eink
M module-services/service-eink/messages/EinkMessage.hpp => module-services/service-eink/messages/EinkMessage.hpp +2 -3
@@ 8,11 8,10 @@
namespace service::eink
{
-
class EinkMessage : public sys::DataMessage
{
public:
- EinkMessage() : sys::DataMessage(MessageType::EinkMessage){};
+ EinkMessage() : sys::DataMessage(MessageType::EinkMessage)
+ {}
};
-
} /* namespace seink */
M module-services/service-eink/messages/EinkModeMessage.hpp => module-services/service-eink/messages/EinkModeMessage.hpp +1 -1
@@ 16,7 16,7 @@ namespace service::eink
Invert
};
- EinkModeMessage(Mode mode) : EinkMessage(), mode(mode)
+ explicit EinkModeMessage(Mode mode) : mode(mode)
{}
[[nodiscard]] auto getMode() const noexcept
M module-services/service-eink/messages/ImageMessage.cpp => module-services/service-eink/messages/ImageMessage.cpp +24 -6
@@ 4,14 4,32 @@
#include "EinkMessage.hpp"
#include "ImageMessage.hpp"
-#include <MessageType.hpp>
-
namespace service::eink
{
+ ImageMessage::ImageMessage(int contextId, ::gui::Context *context, ::gui::RefreshModes refreshMode)
+ : contextId{contextId}, context{context}, refreshMode{refreshMode}
+ {}
+
+ auto ImageMessage::getData() noexcept -> std::uint8_t *
+ {
+ return context->getData();
+ }
- ImageMessage::ImageMessage(
- uint32_t x, uint32_t y, uint32_t w, uint32_t h, bool deepRefresh, uint8_t *data, bool suspend, bool shutdown)
- : EinkMessage(), x{x}, y{y}, w{w}, h{h}, deepRefresh{deepRefresh}, data{data}, suspend{suspend}, shutdown{
- shutdown}
+ auto ImageMessage::getRefreshMode() const noexcept -> ::gui::RefreshModes
+ {
+ return refreshMode;
+ }
+
+ auto ImageMessage::getContextId() const noexcept -> int
+ {
+ return contextId;
+ }
+
+ ImageDisplayedNotification::ImageDisplayedNotification(int contextId) : contextId{contextId}
{}
+
+ auto ImageDisplayedNotification::getContextId() const noexcept -> int
+ {
+ return contextId;
+ }
} /* namespace seink */
M module-services/service-eink/messages/ImageMessage.hpp => module-services/service-eink/messages/ImageMessage.hpp +22 -37
@@ 6,49 6,34 @@
#include "EinkMessage.hpp"
#include <cstdint>
+#include <module-gui/gui/core/Context.hpp>
+#include <module-gui/gui/Common.hpp>
namespace service::eink
{
-
class ImageMessage : public EinkMessage
{
- protected:
- uint32_t x, y, w, h;
- bool deepRefresh;
- uint8_t *data;
- bool suspend = false;
- bool shutdown = false;
-
public:
- ImageMessage(uint32_t x,
- uint32_t y,
- uint32_t w,
- uint32_t h,
- bool deepRefresh,
- uint8_t *data,
- bool suspend,
- bool shutdown);
-
- uint8_t *getData()
- {
- return data;
- };
- uint32_t getSize()
- {
- return w * h;
- };
- bool getDeepRefresh()
- {
- return deepRefresh;
- };
- bool getSuspend()
- {
- return suspend;
- };
- bool getShutdown()
- {
- return shutdown;
- };
+ ImageMessage(int contextId, ::gui::Context *context, ::gui::RefreshModes refreshMode);
+
+ [[nodiscard]] auto getContextId() const noexcept -> int;
+ [[nodiscard]] auto getData() noexcept -> std::uint8_t *;
+ [[nodiscard]] auto getRefreshMode() const noexcept -> ::gui::RefreshModes;
+
+ private:
+ int contextId;
+ ::gui::Context *context;
+ ::gui::RefreshModes refreshMode;
};
+ class ImageDisplayedNotification : public EinkMessage
+ {
+ public:
+ explicit ImageDisplayedNotification(int contextId);
+
+ [[nodiscard]] auto getContextId() const noexcept -> int;
+
+ private:
+ int contextId;
+ };
} /* namespace seink */
A module-services/service-eink/messages/PrepareDisplayRequest.cpp => module-services/service-eink/messages/PrepareDisplayRequest.cpp +15 -0
@@ 0,0 1,15 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "PrepareDisplayRequest.hpp"
+
+namespace service::eink
+{
+ PrepareDisplayRequest::PrepareDisplayRequest(::gui::RefreshModes refreshMode) : refreshMode{refreshMode}
+ {}
+
+ auto PrepareDisplayRequest::getRefreshMode() const noexcept -> ::gui::RefreshModes
+ {
+ return refreshMode;
+ }
+} // namespace service::eink
A module-services/service-eink/messages/PrepareDisplayRequest.hpp => module-services/service-eink/messages/PrepareDisplayRequest.hpp +22 -0
@@ 0,0 1,22 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "EinkMessage.hpp"
+
+#include <gui/Common.hpp>
+
+namespace service::eink
+{
+ class PrepareDisplayRequest : public EinkMessage
+ {
+ public:
+ explicit PrepareDisplayRequest(::gui::RefreshModes refreshMode);
+
+ [[nodiscard]] auto getRefreshMode() const noexcept -> ::gui::RefreshModes;
+
+ private:
+ ::gui::RefreshModes refreshMode;
+ };
+} // namespace service::eink
D module-services/service-eink/messages/StateRequest.hpp => module-services/service-eink/messages/StateRequest.hpp +0 -12
@@ 1,12 0,0 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#pragma once
-
-#include "EinkMessage.hpp"
-
-namespace service::eink
-{
- class StateRequest : public EinkMessage
- {};
-} // namespace service::eink
D module-services/service-eink/messages/TemperatureUpdate.hpp => module-services/service-eink/messages/TemperatureUpdate.hpp +0 -12
@@ 1,12 0,0 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#pragma once
-
-#include "EinkMessage.hpp"
-
-namespace service::eink
-{
- class TemperatureUpdate : public EinkMessage
- {};
-} // namespace service::eink
M module-services/service-gui/CMakeLists.txt => module-services/service-gui/CMakeLists.txt +7 -2
@@ 4,17 4,19 @@ message( "${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}" )
set(SOURCES
"${CMAKE_CURRENT_LIST_DIR}/ServiceGUI.cpp"
"${CMAKE_CURRENT_LIST_DIR}/WorkerGUI.cpp"
+ "${CMAKE_CURRENT_LIST_DIR}/ContextPool.cpp"
+ "${CMAKE_CURRENT_LIST_DIR}/DrawCommandsQueue.cpp"
+ "${CMAKE_CURRENT_LIST_DIR}/SynchronizationMechanism.cpp"
"${CMAKE_CURRENT_LIST_DIR}/messages/DrawMessage.cpp"
)
-
add_library(${PROJECT_NAME} STATIC ${SOURCES})
target_link_libraries(${PROJECT_NAME}
service-eink
module-gui
module-apps
- )
+)
target_include_directories(${PROJECT_NAME}
PUBLIC
@@ 22,3 24,6 @@ target_include_directories(${PROJECT_NAME}
"${CMAKE_CURRENT_LIST_DIR}/messages"
)
+if (${ENABLE_TESTS})
+ add_subdirectory(tests)
+endif()
M module-services/service-gui/Common.hpp => module-services/service-gui/Common.hpp +1 -1
@@ 6,4 6,4 @@
namespace service::name
{
constexpr inline auto gui = "ServiceGUI";
-}
+} // namespace service::name
A module-services/service-gui/ContextPool.cpp => module-services/service-gui/ContextPool.cpp +109 -0
@@ 0,0 1,109 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "ContextPool.hpp"
+
+#include <mutex.hpp>
+
+#include <algorithm>
+
+namespace service::gui
+{
+ using namespace std::literals::chrono_literals;
+
+ namespace
+ {
+ std::unique_ptr<::gui::Context> allocateContext(::gui::Size screenSize)
+ {
+ return std::make_unique<::gui::Context>(screenSize.width, screenSize.height);
+ }
+
+ constexpr auto DefaultWaitTime = 5000ms;
+ } // namespace
+
+ ContextPool::ContextPool(::gui::Size screenSize,
+ std::size_t capacity,
+ std::unique_ptr<SynchronizationMechanism> &&synchronization)
+ : synchronization{std::move(synchronization)}
+ {
+ contexts.reserve(capacity);
+ freeContextIds.reserve(capacity);
+ lockedContextIds.reserve(capacity);
+
+ for (std::size_t i = 0; i < capacity; ++i) {
+ contexts.push_back(allocateContext(screenSize));
+ freeContextIds.push_back(i);
+ }
+ }
+
+ auto ContextPool::peekContext(int id) noexcept -> ::gui::Context *
+ {
+ return contexts[id].get();
+ }
+
+ auto ContextPool::borrowContext() -> std::pair<int, ::gui::Context *>
+ {
+ cpp_freertos::LockGuard lock(mutex);
+ synchronization->wait(mutex, DefaultWaitTime, [this]() { return isAvailable(); });
+
+ const auto contextId = freeContextIds.front();
+ lockContext(contextId);
+ return std::make_pair(contextId, contexts[contextId].get());
+ }
+
+ auto ContextPool::borrowContext(int id) -> ::gui::Context *
+ {
+ cpp_freertos::LockGuard lock(mutex);
+ const auto it = std::find(freeContextIds.begin(), freeContextIds.end(), id);
+ if (it == freeContextIds.end()) {
+ return nullptr;
+ }
+
+ lockContext(id);
+ return contexts[id].get();
+ }
+
+ auto ContextPool::isAnyContextLocked() const -> bool
+ {
+ cpp_freertos::LockGuard lock(mutex);
+ return !lockedContextIds.empty();
+ }
+
+ auto ContextPool::isAvailable() const noexcept -> bool
+ {
+ return !freeContextIds.empty();
+ }
+
+ void ContextPool::lockContext(int contextId)
+ {
+ removeContextId(freeContextIds, contextId);
+ addContextId(lockedContextIds, contextId);
+ }
+
+ void ContextPool::returnContext(int id)
+ {
+ cpp_freertos::LockGuard lock(mutex);
+ freeContext(id);
+
+ synchronization->notify();
+ }
+
+ void ContextPool::freeContext(int contextId)
+ {
+ removeContextId(lockedContextIds, contextId);
+ addContextId(freeContextIds, contextId);
+ }
+
+ void ContextPool::removeContextId(std::vector<int> &sequence, int contextId)
+ {
+ sequence.erase(std::remove(sequence.begin(), sequence.end(), contextId), sequence.end());
+ }
+
+ void ContextPool::addContextId(std::vector<int> &sequence, int contextId)
+ {
+ if (const auto it = std::find(sequence.begin(), sequence.end(), contextId); it == sequence.end()) {
+ // Add if not found.
+ sequence.push_back(contextId);
+ }
+ }
+} // namespace service::gui
A module-services/service-gui/ContextPool.hpp => module-services/service-gui/ContextPool.hpp +74 -0
@@ 0,0 1,74 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "SynchronizationMechanism.hpp"
+
+#include <gui/Common.hpp>
+#include <gui/core/Context.hpp>
+
+#include <mutex.hpp>
+
+#include <chrono>
+#include <functional>
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace service::gui
+{
+ class ContextPool
+ {
+ public:
+ ContextPool(
+ ::gui::Size screenSize,
+ std::size_t capacity,
+ std::unique_ptr<SynchronizationMechanism> &&synchronization = getFreeRtosSynchronizationMechanism());
+
+ /**
+ * Peeks the context under specific id
+ * @param id Context id
+ * @return Context data
+ */
+ [[nodiscard]] auto peekContext(int id) noexcept -> ::gui::Context *;
+ /**
+ * Locks the first available context
+ * @return A context and its id
+ */
+ [[nodiscard]] auto borrowContext() -> std::pair<int, ::gui::Context *>;
+ /**
+ * Locks the context
+ * @param id Context id
+ * @return Context data
+ */
+ [[nodiscard]] auto borrowContext(int id) -> ::gui::Context *;
+ /**
+ * Checks whether any context is currently locked
+ * @return True if any context is locked, false otherwise
+ */
+ [[nodiscard]] auto isAnyContextLocked() const -> bool;
+ /**
+ * Makes a context (specified by its id) available again.
+ * @param id Context id
+ */
+ void returnContext(int id);
+
+ private:
+ using ContextIdContainer = std::vector<int>;
+
+ [[nodiscard]] auto isAvailable() const noexcept -> bool;
+
+ void freeContext(int contextId);
+ void lockContext(int contextId);
+ static void removeContextId(std::vector<int> &sequence, int contextId);
+ static void addContextId(std::vector<int> &sequence, int contextId);
+
+ std::vector<std::unique_ptr<::gui::Context>> contexts;
+ ContextIdContainer lockedContextIds;
+ ContextIdContainer freeContextIds;
+
+ mutable cpp_freertos::MutexStandard mutex;
+ std::unique_ptr<SynchronizationMechanism> synchronization;
+ };
+} // namespace service::gui
A module-services/service-gui/DrawCommandsQueue.cpp => module-services/service-gui/DrawCommandsQueue.cpp +62 -0
@@ 0,0 1,62 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "DrawCommandsQueue.hpp"
+
+namespace service::gui
+{
+ namespace
+ {
+ constexpr std::chrono::milliseconds WaitTimeout{5000};
+ } // namespace
+
+ DrawCommandsQueue::DrawCommandsQueue(std::size_t expectedSize,
+ std::unique_ptr<SynchronizationMechanism> &&synchronization)
+ : synchronization{std::move(synchronization)}
+ {
+ queue.reserve(expectedSize);
+ }
+
+ void DrawCommandsQueue::enqueue(QueueItem &&item)
+ {
+ cpp_freertos::LockGuard lock{queueMutex};
+ queue.push_back(std::move(item));
+
+ synchronization->notify();
+ }
+
+ auto DrawCommandsQueue::dequeue() -> QueueItem
+ {
+ cpp_freertos::LockGuard lock{queueMutex};
+ synchronization->wait(queueMutex, WaitTimeout, [this]() { return !queue.empty(); });
+
+ auto item = std::move(queue.front());
+ queue.erase(queue.begin());
+ return item;
+ }
+
+ auto DrawCommandsQueue::getMaxRefreshModeAndClear() -> ::gui::RefreshModes
+ {
+ cpp_freertos::LockGuard lock{queueMutex};
+ const auto deepRefreshRequested = std::any_of(queue.begin(), queue.end(), [](const auto &item) {
+ return item.refreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP;
+ });
+ const auto maxRefreshMode =
+ deepRefreshRequested ? ::gui::RefreshModes::GUI_REFRESH_DEEP : ::gui::RefreshModes::GUI_REFRESH_FAST;
+
+ queue.clear();
+ return maxRefreshMode;
+ }
+
+ void DrawCommandsQueue::clear()
+ {
+ cpp_freertos::LockGuard lock{queueMutex};
+ queue.clear();
+ }
+
+ auto DrawCommandsQueue::size() const noexcept -> QueueContainer::size_type
+ {
+ cpp_freertos::LockGuard lock{queueMutex};
+ return queue.size();
+ }
+} // namespace service::gui
A module-services/service-gui/DrawCommandsQueue.hpp => module-services/service-gui/DrawCommandsQueue.hpp +44 -0
@@ 0,0 1,44 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "SynchronizationMechanism.hpp"
+
+#include <gui/core/DrawCommand.hpp>
+
+#include <cstdint>
+#include <list>
+#include <memory>
+#include <vector>
+
+namespace service::gui
+{
+ class DrawCommandsQueue
+ {
+ public:
+ using CommandList = std::list<std::unique_ptr<::gui::DrawCommand>>;
+ struct QueueItem
+ {
+ CommandList commands;
+ ::gui::RefreshModes refreshMode = ::gui::RefreshModes::GUI_REFRESH_FAST;
+ };
+ using QueueContainer = std::vector<QueueItem>;
+
+ explicit DrawCommandsQueue(
+ std::size_t expectedSize,
+ std::unique_ptr<SynchronizationMechanism> &&synchronization = getFreeRtosSynchronizationMechanism());
+
+ void enqueue(QueueItem &&item);
+ [[nodiscard]] auto dequeue() -> QueueItem;
+ [[nodiscard]] auto getMaxRefreshModeAndClear() -> ::gui::RefreshModes;
+ void clear();
+ [[nodiscard]] auto size() const noexcept -> QueueContainer::size_type;
+
+ private:
+ QueueContainer queue;
+
+ mutable cpp_freertos::MutexStandard queueMutex;
+ std::unique_ptr<SynchronizationMechanism> synchronization;
+ };
+} // namespace service::gui
M module-services/service-gui/ServiceGUI.cpp => module-services/service-gui/ServiceGUI.cpp +167 -166
@@ 4,245 4,246 @@
#include "ServiceGUI.hpp"
#include "WorkerGUI.hpp"
-#include "messages/GUIMessage.hpp"
#include "messages/DrawMessage.hpp"
-#include "messages/RenderingFinished.hpp"
-#include "messages/GUIDisplayReady.hpp"
+#include "messages/EinkReady.hpp"
#include <DrawCommand.hpp>
#include <FontManager.hpp>
-#include <MessageType.hpp>
-#include <gui/core/Context.hpp>
#include <gui/core/ImageManager.hpp>
#include <log/log.hpp>
-#include <projdefs.h>
-#include <service-appmgr/Controller.hpp>
+#include <service-eink/Common.hpp>
#include <service-eink/messages/ImageMessage.hpp>
#include <service-eink/messages/EinkMessage.hpp>
-#include <service-eink/messages/StateRequest.hpp>
-#include <Service/Bus.hpp>
-#include <Service/Worker.hpp>
+#include <service-eink/messages/PrepareDisplayRequest.hpp>
#include <SystemManager/SystemManager.hpp>
-#include <service-eink/Common.hpp>
-#include <task.h>
-
-extern "C"
-{
-#include <FreeRTOS.h>
-#include <semphr.h>
-}
-
-#include <cstddef>
-#include <list>
-#include <memory>
-#include <utility>
+#include <gsl/gsl_util>
#include <purefs/filesystem_paths.hpp>
-namespace sgui
+namespace service::gui
{
- ServiceGUI::ServiceGUI(const std::string &name, std::string parent, uint32_t screenWidth, uint32_t screenHeight)
- : sys::Service(name, parent, 4096, sys::ServicePriority::Idle), renderContext{nullptr},
- transferContext{nullptr}, renderFrameCounter{1}, transferedFrameCounter{0}, screenWidth{screenWidth},
- screenHeight{screenHeight}, semCommands{NULL}
+ namespace
{
-
- LOG_INFO("[ServiceGUI] Initializing");
-
- renderContext = new gui::Context(screenWidth, screenHeight);
- transferContext = new gui::Context(screenWidth, screenHeight);
-
- gui::FontManager &fontManager = gui::FontManager::getInstance();
- fontManager.init(purefs::dir::getCurrentOSPath() / "assets");
-
- gui::ImageManager &imageManager = gui::ImageManager::getInstance();
- imageManager.init(purefs::dir::getCurrentOSPath() / "assets");
-
- connect(typeid(sgui::DrawMessage),
- [&](sys::Message *request) -> sys::MessagePointer { return handleDrawMessage(request); });
-
- connect(typeid(service::gui::RenderingFinished),
- [&](sys::Message *request) -> sys::MessagePointer { return handleGUIRenderingFinished(request); });
-
- connect(typeid(service::gui::GUIDisplayReady),
- [&](sys::Message *request) -> sys::MessagePointer { return handleGUIDisplayReady(request); });
+ constexpr auto ServiceGuiStackDepth = 4096U;
+ constexpr auto ContextsCount = 2;
+ constexpr auto CommandsQueueCapacity = 3;
+ constexpr std::chrono::milliseconds ContextReleaseTimeout{1000};
+ } // namespace
+
+ ServiceGUI::ServiceGUI(const std::string &name, std::string parent)
+ : sys::Service(name, parent, ServiceGuiStackDepth), commandsQueue{std::make_unique<DrawCommandsQueue>(
+ CommandsQueueCapacity)},
+ contextReleaseTimer{
+ std::make_unique<sys::Timer>(this, ContextReleaseTimeout.count(), sys::Timer::Type::SingleShot)},
+ currentState{State::NotInitialised}
+ {
+ initAssetManagers();
+ registerMessageHandlers();
}
- ServiceGUI::~ServiceGUI()
+ ServiceGUI::~ServiceGUI() noexcept = default;
+
+ void ServiceGUI::initAssetManagers()
{
- LOG_INFO("[ServiceGUI] Cleaning resources");
- if (renderContext)
- delete renderContext;
- if (transferContext)
- delete transferContext;
+ const auto assetsPath = purefs::dir::getCurrentOSPath() / "assets";
+ ::gui::FontManager::getInstance().init(assetsPath);
+ ::gui::ImageManager::getInstance().init(assetsPath);
}
- void ServiceGUI::sendBuffer()
+ void ServiceGUI::registerMessageHandlers()
{
- transferContext->insert(0, 0, renderContext);
+ connect(typeid(EinkReady),
+ [this](sys::Message *request) -> sys::MessagePointer { return handleEinkReady(request); });
- auto msg =
- std::make_shared<service::eink::ImageMessage>(0,
- 0,
- transferContext->getW(),
- transferContext->getH(),
- (mode == gui::RefreshModes::GUI_REFRESH_DEEP ? true : false),
- transferContext->getData(),
- suspendInProgress,
- shutdownInProgress);
- einkReady = false;
- auto ret = sys::Bus::SendUnicast(msg, service::name::eink, this, 2000);
- if (ret.first == sys::ReturnCodes::Success) {
- transferedFrameCounter = renderFrameCounter;
- }
- mode = gui::RefreshModes::GUI_REFRESH_FAST;
- }
+ connect(typeid(DrawMessage),
+ [this](sys::Message *request) -> sys::MessagePointer { return handleDrawMessage(request); });
- void ServiceGUI::sendToRender()
- {
- rendering = true;
- worker->send(static_cast<uint32_t>(WorkerGUICommands::Render), NULL);
+ connect(typeid(RenderingFinished),
+ [this](sys::Message *request) -> sys::MessagePointer { return handleGUIRenderingFinished(request); });
+
+ connect(typeid(eink::ImageDisplayedNotification), [this](sys::Message *request) -> sys::MessagePointer {
+ return handleImageDisplayedNotification(request);
+ });
}
sys::MessagePointer ServiceGUI::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)
{
- return std::make_shared<sys::ResponseMessage>();
+ return sys::MessageNone{};
}
sys::ReturnCodes ServiceGUI::InitHandler()
{
- semCommands = xSemaphoreCreateBinary();
- if (semCommands == NULL) {
- LOG_FATAL("Failed to create commands semaphore.");
- return sys::ReturnCodes::Failure;
- }
- xSemaphoreGive(semCommands);
-
+ std::list<sys::WorkerQueueInfo> queueInfo{
+ {WorkerGUI::SignallingQueueName, WorkerGUI::SignalSize, WorkerGUI::SignallingQueueCapacity}};
worker = std::make_unique<WorkerGUI>(this);
- std::list<sys::WorkerQueueInfo> list;
- worker->init(list);
+ worker->init(queueInfo);
worker->run();
-
- if (einkReady == false) {
- requestSent = true;
- sys::Bus::SendUnicast(std::make_shared<service::eink::StateRequest>(), service::name::eink, this);
- }
return sys::ReturnCodes::Success;
}
sys::ReturnCodes ServiceGUI::DeinitHandler()
{
-
- if (semCommands != NULL)
- vSemaphoreDelete(semCommands);
- semCommands = NULL;
-
- worker->stop();
- worker->join();
- worker->deinit();
-
+ worker->close();
return sys::ReturnCodes::Success;
}
sys::ReturnCodes ServiceGUI::SwitchPowerModeHandler(const sys::ServicePowerMode mode)
{
- LOG_FATAL("[ServiceGUI] PowerModeHandler: %s", c_str(mode));
-
+ LOG_INFO("PowerModeHandler: %s", c_str(mode));
switch (mode) {
- case sys::ServicePowerMode ::Active:
+ case sys::ServicePowerMode::Active:
+ setState(contextPool != nullptr ? State::Idle : State::NotInitialised);
break;
- case sys::ServicePowerMode ::SuspendToRAM:
- case sys::ServicePowerMode ::SuspendToNVM:
+ case sys::ServicePowerMode::SuspendToRAM:
+ [[fallthrough]];
+ case sys::ServicePowerMode::SuspendToNVM:
+ setState(State::Suspended);
break;
}
-
return sys::ReturnCodes::Success;
}
sys::MessagePointer ServiceGUI::handleDrawMessage(sys::Message *message)
{
- auto dmsg = static_cast<sgui::DrawMessage *>(message);
- if (!dmsg->commands.empty()) {
-
- if (!suspendInProgress) {
+ if (isInState(State::NotInitialised)) {
+ LOG_WARN("Service not yet initialised - ignoring draw commands");
+ return sys::MessageNone{};
+ }
+ if (isInState(State::Suspended)) {
+ LOG_WARN("Suspended - ignoring draw commands");
+ return sys::MessageNone{};
+ }
- if (dmsg->isType(sgui::DrawMessage::Type::SHUTDOWN)) {
- LOG_WARN("Shutdown - received shutdown draw commands");
- shutdownInProgress = true;
- }
+ if (const auto drawMsg = static_cast<DrawMessage *>(message); !drawMsg->commands.empty()) {
+ if (drawMsg->isType(DrawMessage::Type::SHUTDOWN) || drawMsg->isType(DrawMessage::Type::SUSPEND)) {
+ setState(State::Suspended);
+ }
- if (dmsg->isType(sgui::DrawMessage::Type::SUSPEND)) {
- LOG_WARN("Suspended - received suspend draw commands");
- suspendInProgress = true;
- }
+ prepareDisplay(drawMsg->mode);
+ notifyRenderer(std::move(drawMsg->commands), drawMsg->mode);
+ }
+ return sys::MessageNone{};
+ }
- if (dmsg->mode == gui::RefreshModes::GUI_REFRESH_DEEP) {
- mode = dmsg->mode;
- }
+ void ServiceGUI::prepareDisplay(::gui::RefreshModes refreshMode)
+ {
+ auto msg = std::make_shared<service::eink::PrepareDisplayRequest>(refreshMode);
+ sys::Bus::SendUnicast(msg, service::name::eink, this);
+ }
- if (xSemaphoreTake(semCommands, pdMS_TO_TICKS(1000)) == pdTRUE) {
- commands = std::move(dmsg->commands);
- xSemaphoreGive(semCommands);
- }
- else {
- LOG_ERROR("Failed to acquire semaphore");
- }
+ void ServiceGUI::notifyRenderer(std::list<std::unique_ptr<::gui::DrawCommand>> &&commands,
+ ::gui::RefreshModes refreshMode)
+ {
+ enqueueDrawCommands(DrawCommandsQueue::QueueItem{std::move(commands), refreshMode});
+ worker->notify(WorkerGUI::Signal::Render);
+ }
- if (!rendering) {
- sendToRender();
- }
- }
- else {
- LOG_WARN("Suspended - ignoring draw commands");
- }
+ void ServiceGUI::enqueueDrawCommands(DrawCommandsQueue::QueueItem &&item)
+ {
+ // Clear all queue elements for now to keep only the latest command in the queue.
+ // In the future, we'll need to implement more sophisticated algorithm for partially refresh the display.
+ if (item.refreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP) {
+ commandsQueue->clear();
+ }
+ else if (const auto maxRefreshMode = commandsQueue->getMaxRefreshModeAndClear();
+ maxRefreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP) {
+ item.refreshMode = maxRefreshMode;
}
- return nullptr;
+ commandsQueue->enqueue(std::move(item)); // 3 consecutive deep refreshes after leaving messages? :/
}
sys::MessagePointer ServiceGUI::handleGUIRenderingFinished(sys::Message *message)
{
- rendering = false;
- renderFrameCounter++;
-
- if (einkReady) {
- sendBuffer();
+ auto finishedMsg = static_cast<service::gui::RenderingFinished *>(message);
+ const auto contextId = finishedMsg->getContextId();
+ const auto refreshMode = finishedMsg->getRefreshMode();
+ if (isInState(State::Idle)) {
+ const auto context = contextPool->peekContext(contextId);
+ sendOnDisplay(context, contextId, refreshMode);
+ invalidateCache();
}
- else if (!requestSent) {
- requestSent = true;
- sys::Bus::SendUnicast(std::make_shared<service::eink::StateRequest>(), service::name::eink, this);
+ else {
+ cacheRender(contextId, refreshMode);
+ contextPool->returnContext(contextId);
}
- return nullptr;
+ return sys::MessageNone{};
+ }
+
+ void ServiceGUI::sendOnDisplay(::gui::Context *context, int contextId, ::gui::RefreshModes refreshMode)
+ {
+ setState(State::Busy);
+ auto imageMsg = std::make_shared<service::eink::ImageMessage>(contextId, context, refreshMode);
+ sys::Bus::SendUnicast(imageMsg, service::name::eink, this);
+ scheduleContextRelease(contextId);
}
- sys::MessagePointer ServiceGUI::handleGUIDisplayReady(sys::Message *message)
+ void ServiceGUI::scheduleContextRelease(int contextId)
{
- auto msg = static_cast<service::gui::GUIDisplayReady *>(message);
- einkReady = true;
- requestSent = false;
+ // Whenever the response from ServiceEink doesn't come, the context has to be released automatically after a
+ // timeout.
+ contextReleaseTimer->connect([this, contextId](sys::Timer &it) {
+ eink::ImageDisplayedNotification notification{contextId};
+ handleImageDisplayedNotification(¬ification);
+ LOG_WARN("Context # %d released after timeout. Does ServiceEink respond properly?", contextId);
+ });
+ contextReleaseTimer->start();
+ }
- if (msg->getShutdownInProgress()) {
- einkReady = false;
- suspendInProgress = false;
- LOG_DEBUG("last rendering before shutdown finished.");
+ void ServiceGUI::cacheRender(int contextId, ::gui::RefreshModes refreshMode)
+ {
+ cachedRender = CachedRender{contextId, refreshMode};
+ }
- sys::SystemManager::CloseSystem(this);
- }
+ void ServiceGUI::invalidateCache()
+ {
+ cachedRender = std::nullopt;
+ }
- if (msg->getSuspendInProgress()) {
- einkReady = false;
- suspendInProgress = false;
- LOG_DEBUG("last rendering before suspend is finished.");
+ sys::MessagePointer ServiceGUI::handleEinkReady(sys::Message *message)
+ {
+ const auto msg = static_cast<service::gui::EinkReady *>(message);
+ contextPool = std::make_unique<ContextPool>(msg->getDisplaySize(), ContextsCount);
+ setState(State::Idle);
+ return sys::MessageNone{};
+ }
- app::manager::Controller::changePowerSaveMode(this);
- }
- if ((renderFrameCounter != transferedFrameCounter) && (!rendering)) {
- sendBuffer();
+ sys::MessagePointer ServiceGUI::handleImageDisplayedNotification(sys::Message *message)
+ {
+ const auto msg = static_cast<eink::ImageDisplayedNotification *>(message);
+ const auto contextId = msg->getContextId();
+ contextPool->returnContext(contextId);
+ contextReleaseTimer->stop();
+ setState(State::Idle);
+
+ if (isNextFrameReady()) {
+ trySendNextFrame();
}
+ return sys::MessageNone{};
+ }
+
+ bool ServiceGUI::isNextFrameReady() const noexcept
+ {
+ // Even if the next render is already cached, if any context in the pool is currently being processed, then we
+ // better wait for it.
+ return cachedRender.has_value() && !contextPool->isAnyContextLocked();
+ }
- if (commands.empty() == false) {
- sendToRender();
+ void ServiceGUI::trySendNextFrame()
+ {
+ const auto contextId = cachedRender->contextId;
+ if (const auto context = contextPool->borrowContext(contextId); context != nullptr) {
+ sendOnDisplay(context, contextId, cachedRender->refreshMode);
}
- return nullptr;
+ invalidateCache();
+ }
+
+ void ServiceGUI::setState(State state) noexcept
+ {
+ currentState = state;
}
+ bool ServiceGUI::isInState(State state) const noexcept
+ {
+ return currentState == state;
+ }
} /* namespace sgui */
M module-services/service-gui/ServiceGUI.hpp => module-services/service-gui/ServiceGUI.hpp +51 -63
@@ 3,20 3,20 @@
#pragma once
-// module-gui
-
#include <Common.hpp>
-#include <gui/core/Context.hpp>
-#include <gui/core/Renderer.hpp>
-#include <gui/input/Translator.hpp>
#include <Service/Common.hpp>
#include <Service/Message.hpp>
#include <Service/Service.hpp>
+#include <Service/Timer.hpp>
+
+#include "messages/RenderingFinished.hpp"
+
+#include "ContextPool.hpp"
+#include "DrawCommandsQueue.hpp"
#include <cstdint>
#include <memory>
#include <string>
-#include <vector>
namespace gui
{
@@ 24,76 24,64 @@ namespace gui
class DrawCommand;
} // namespace gui
-namespace sgui
+namespace service::gui
{
-
class WorkerGUI;
class ServiceGUI : public sys::Service
{
friend WorkerGUI;
- protected:
- // this is where every incomming frame is painted.
- gui::Context *renderContext;
- // this buffer is provided to eink
- gui::Context *transferContext;
- // ID of the last rendered frame
- uint32_t renderFrameCounter;
- // ID of the last frame sent to eink for rendering
- uint32_t transferedFrameCounter;
- // Horizontal size of the screen in pixels
- uint32_t screenWidth;
- // vertical size of the screen in pixels
- uint32_t screenHeight;
- // object responsible for rendering images to context
- gui::Renderer renderer;
- // flag that defines whether eink is ready for new frame buffer
- volatile bool einkReady = false;
- volatile bool requestSent = false;
- volatile bool rendering = false;
- // set of commands recently received. If this vector is not empty and new set of commands is received
- // previous commands are removed.
- std::list<std::unique_ptr<gui::DrawCommand>> commands;
- // uint32_t timer_id= 0;
- gui::RefreshModes mode = gui::RefreshModes::GUI_REFRESH_DEEP;
-
- // semaphore used to protect commands vector while commands are taken from service to worker.
- SemaphoreHandle_t semCommands;
-
- std::unique_ptr<WorkerGUI> worker;
-
- /**
- * Flag controls process of redrawing screen when suspend is in progress.
- */
- bool suspendInProgress = false;
- /**
- * Flag controls process of redrawing screen when phone is shutting down.
- */
- bool shutdownInProgress = false;
-
- void sendBuffer();
- void sendToRender();
-
public:
- ServiceGUI(const std::string &name,
- std::string parent = "",
- uint32_t screenWidth = 480,
- uint32_t screenHeight = 600);
- ~ServiceGUI();
-
- sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) override;
+ explicit ServiceGUI(const std::string &name, std::string parent = {});
+ ~ServiceGUI() noexcept override;
sys::ReturnCodes InitHandler() override;
-
sys::ReturnCodes DeinitHandler() override;
-
- sys::ReturnCodes SwitchPowerModeHandler(const sys::ServicePowerMode mode) override final;
+ sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) override;
+ sys::ReturnCodes SwitchPowerModeHandler(const sys::ServicePowerMode mode) override;
private:
+ struct CachedRender
+ {
+ int contextId;
+ ::gui::RefreshModes refreshMode;
+ };
+ enum class State
+ {
+ NotInitialised,
+ Idle,
+ Busy,
+ Suspended
+ };
+
+ static void initAssetManagers();
+ void registerMessageHandlers();
+
+ void cacheRender(int contextId, ::gui::RefreshModes refreshMode);
+ void invalidateCache();
+
+ void prepareDisplay(::gui::RefreshModes refreshMode);
+ void notifyRenderer(std::list<std::unique_ptr<::gui::DrawCommand>> &&commands, ::gui::RefreshModes refreshMode);
+ void enqueueDrawCommands(DrawCommandsQueue::QueueItem &&item);
+ void sendOnDisplay(::gui::Context *context, int contextId, ::gui::RefreshModes refreshMode);
+ void scheduleContextRelease(int contextId);
+ bool isNextFrameReady() const noexcept;
+ void trySendNextFrame();
+
+ void setState(State state) noexcept;
+ bool isInState(State state) const noexcept;
+
sys::MessagePointer handleDrawMessage(sys::Message *message);
sys::MessagePointer handleGUIRenderingFinished(sys::Message *message);
- sys::MessagePointer handleGUIDisplayReady(sys::Message *message);
- };
+ sys::MessagePointer handleEinkReady(sys::Message *message);
+ sys::MessagePointer handleImageDisplayedNotification(sys::Message *message);
-} /* namespace sgui */
+ std::unique_ptr<ContextPool> contextPool;
+ std::unique_ptr<WorkerGUI> worker;
+ std::unique_ptr<DrawCommandsQueue> commandsQueue;
+ std::optional<CachedRender> cachedRender;
+ std::unique_ptr<sys::Timer> contextReleaseTimer;
+ State currentState;
+ };
+} // namespace service::gui
A module-services/service-gui/SynchronizationMechanism.cpp => module-services/service-gui/SynchronizationMechanism.cpp +58 -0
@@ 0,0 1,58 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "SynchronizationMechanism.hpp"
+
+namespace service::gui
+{
+ class FreeRtosSynchronization : public SynchronizationMechanism
+ {
+ public:
+ FreeRtosSynchronization();
+ FreeRtosSynchronization(const FreeRtosSynchronization &) = delete;
+ FreeRtosSynchronization(FreeRtosSynchronization &&) noexcept = default;
+ FreeRtosSynchronization &operator=(const FreeRtosSynchronization &) = delete;
+ FreeRtosSynchronization &operator=(FreeRtosSynchronization &&) noexcept = default;
+ ~FreeRtosSynchronization() noexcept override;
+
+ void wait(cpp_freertos::Mutex &lock, std::chrono::milliseconds timeout, Predicate predicate) override;
+ void notify() override;
+
+ private:
+ SemaphoreHandle_t semaphore;
+ };
+
+ FreeRtosSynchronization::FreeRtosSynchronization() : semaphore{xSemaphoreCreateBinary()}
+ {}
+
+ FreeRtosSynchronization::~FreeRtosSynchronization() noexcept
+ {
+ vSemaphoreDelete(semaphore);
+ }
+
+ void FreeRtosSynchronization::wait(cpp_freertos::Mutex &lock,
+ std::chrono::milliseconds timeout,
+ Predicate predicate)
+ {
+ if (!predicate) {
+ // Predicate needed to cover spurious wake-ups.
+ throw std::invalid_argument{"Null predicate passed."};
+ }
+
+ while (!predicate()) {
+ lock.Unlock();
+ xSemaphoreTake(semaphore, timeout.count());
+ lock.Lock();
+ }
+ }
+
+ void FreeRtosSynchronization::notify()
+ {
+ xSemaphoreGive(semaphore);
+ }
+
+ std::unique_ptr<SynchronizationMechanism> getFreeRtosSynchronizationMechanism()
+ {
+ return std::make_unique<FreeRtosSynchronization>();
+ }
+} // namespace service::gui
A module-services/service-gui/SynchronizationMechanism.hpp => module-services/service-gui/SynchronizationMechanism.hpp +26 -0
@@ 0,0 1,26 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <mutex.hpp>
+
+#include <chrono>
+#include <functional>
+#include <memory>
+
+namespace service::gui
+{
+ /// Interface providing platform-independent synchronization strategy.
+ class SynchronizationMechanism
+ {
+ public:
+ using Predicate = std::function<bool()>;
+
+ virtual ~SynchronizationMechanism() noexcept = default;
+ virtual void wait(cpp_freertos::Mutex &lock, std::chrono::milliseconds timeout, Predicate predicate) = 0;
+ virtual void notify() = 0;
+ };
+
+ std::unique_ptr<SynchronizationMechanism> getFreeRtosSynchronizationMechanism();
+} // namespace service::gui
M module-services/service-gui/WorkerGUI.cpp => module-services/service-gui/WorkerGUI.cpp +34 -31
@@ 5,52 5,55 @@
#include <DrawCommand.hpp>
#include <log/log.hpp>
-#include <MessageType.hpp>
-#include <projdefs.h>
-#include <queue.h>
#include <Renderer.hpp>
-#include <semphr.h>
#include <Service/Bus.hpp>
-#include <Service/Message.hpp>
-#include <Service/Service.hpp>
#include <Service/Worker.hpp>
#include <service-gui/ServiceGUI.hpp>
#include <memory>
-#include <utility>
-#include <vector>
+#include <module-utils/time/ScopedTime.hpp>
#include "messages/RenderingFinished.hpp"
-namespace sgui
+namespace service::gui
{
-
- WorkerGUI::WorkerGUI(ServiceGUI *service) : Worker(service), service(service)
+ WorkerGUI::WorkerGUI(ServiceGUI *service) : Worker(service), guiService{service}
{}
- bool WorkerGUI::handleMessage(uint32_t queueID)
+ void WorkerGUI::notify(Signal command)
{
- auto &queue = queues[queueID];
-
- auto serviceGUI = static_cast<sgui::ServiceGUI *>(service);
-
- if (queueID == 0) {
- sys::WorkerCommand received;
- queue->Dequeue(&received, 0);
+ if (auto queue = getQueueByName(SignallingQueueName); !queue->Overwrite(&command)) {
+ LOG_ERROR("Unable to overwrite the command in the commands queue.");
+ }
+ }
- std::list<std::unique_ptr<gui::DrawCommand>> uniqueCommands;
- if (xSemaphoreTake(serviceGUI->semCommands, pdMS_TO_TICKS(1000)) == pdTRUE) {
- uniqueCommands = std::move(serviceGUI->commands);
- xSemaphoreGive(serviceGUI->semCommands);
- }
- else {
- LOG_ERROR("Failed to acquire semaphore");
+ bool WorkerGUI::handleMessage(uint32_t queueID)
+ {
+ if (const auto queue = queues[queueID]; queue->GetQueueName() == SignallingQueueName) {
+ if (sys::WorkerCommand command; queue->Dequeue(&command, 0)) {
+ handleCommand(static_cast<Signal>(command.command));
}
-
- serviceGUI->renderer.render(serviceGUI->renderContext, uniqueCommands);
-
- sys::Bus::SendUnicast(std::make_shared<service::gui::RenderingFinished>(), service->GetName(), service);
}
return true;
}
-} /* namespace sgui */
+ void WorkerGUI::handleCommand(Signal command)
+ {
+ if (command == Signal::Render) {
+ auto item = guiService->commandsQueue->dequeue();
+ render(item.commands, item.refreshMode);
+ }
+ }
+
+ void WorkerGUI::render(std::list<std::unique_ptr<::gui::DrawCommand>> &commands, ::gui::RefreshModes refreshMode)
+ {
+ const auto [contextId, context] = guiService->contextPool->borrowContext(); // Waits for the context.
+ renderer.render(context, commands);
+ onRenderingFinished(contextId, refreshMode);
+ }
+
+ void WorkerGUI::onRenderingFinished(int contextId, ::gui::RefreshModes refreshMode)
+ {
+ auto msg = std::make_shared<service::gui::RenderingFinished>(contextId, refreshMode);
+ sys::Bus::SendUnicast(std::move(msg), guiService->GetName(), guiService);
+ }
+} // namespace service::gui
M module-services/service-gui/WorkerGUI.hpp => module-services/service-gui/WorkerGUI.hpp +22 -24
@@ 12,34 12,32 @@
#include <cstdint>
-namespace sgui
+namespace service::gui
{
-
class ServiceGUI;
- enum class WorkerGUICommands
- {
- Finish,
- Render,
- // RenderSuspend
- };
-
- /*
- *
- */
class WorkerGUI : public sys::Worker
{
- // object responsible for rendering images to context
- gui::Renderer renderer;
- sys::Service *service;
-
public:
- WorkerGUI(ServiceGUI *service);
-
- /**
- * virtual method responsible for finishing the worker and handling rendering commands
- */
- bool handleMessage(uint32_t queueID) override;
+ enum class Signal
+ {
+ Render
+ };
+ static constexpr auto SignallingQueueName = "SignallingQueue";
+ static constexpr auto SignallingQueueCapacity = 1;
+ static constexpr auto SignalSize = sizeof(Signal);
+
+ explicit WorkerGUI(ServiceGUI *service);
+
+ void notify(Signal command);
+ auto handleMessage(std::uint32_t queueID) -> bool override;
+
+ private:
+ void handleCommand(Signal command);
+ void render(std::list<std::unique_ptr<::gui::DrawCommand>> &commands, ::gui::RefreshModes refreshMode);
+ void onRenderingFinished(int contextId, ::gui::RefreshModes refreshMode);
+
+ ServiceGUI *guiService;
+ ::gui::Renderer renderer;
};
-
-} /* namespace sgui */
+} // namespace service::gui
M module-services/service-gui/messages/DrawMessage.cpp => module-services/service-gui/messages/DrawMessage.cpp +4 -6
@@ 6,11 6,9 @@
#include <Common.hpp>
#include <DrawCommand.hpp>
-namespace sgui
+namespace service::gui
{
-
- DrawMessage::DrawMessage(std::list<gui::Command> commands, gui::RefreshModes mode)
+ DrawMessage::DrawMessage(std::list<::gui::Command> commands, ::gui::RefreshModes mode)
: GUIMessage(), mode(mode), commands(std::move(commands))
- {
- }
-} /* namespace sgui */
+ {}
+} // namespace service::gui
M module-services/service-gui/messages/DrawMessage.hpp => module-services/service-gui/messages/DrawMessage.hpp +9 -11
@@ 16,9 16,8 @@
#include "GUIMessage.hpp"
#include "gui/Common.hpp"
-namespace sgui
+namespace service::gui
{
-
class DrawMessage : public GUIMessage
{
public:
@@ 30,20 29,19 @@ namespace sgui
} type = Type::NORMAL;
public:
- gui::RefreshModes mode;
- std::list<gui::Command> commands;
+ ::gui::RefreshModes mode;
+ std::list<::gui::Command> commands;
- DrawMessage(std::list<gui::Command> commandsList, gui::RefreshModes mode);
+ DrawMessage(std::list<::gui::Command> commandsList, ::gui::RefreshModes mode);
- void setCommandType(Type type) noexcept
+ void setCommandType(Type value) noexcept
{
- this->type = type;
+ type = value;
}
- bool isType(Type type) const noexcept
+ bool isType(Type value) const noexcept
{
- return this->type == type;
+ return type == value;
}
};
-
-} // namespace sgui
+} // namespace service::gui
R module-services/service-gui/messages/GUIDisplayReady.hpp => module-services/service-gui/messages/EinkReady.hpp +9 -14
@@ 1,31 1,26 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
#include "GUIMessage.hpp"
+#include <gui/Common.hpp>
+
namespace service::gui
{
- class GUIDisplayReady : public sgui::GUIMessage
+ class EinkReady : public GUIMessage
{
-
- bool suspendInProgress = false;
- bool shutdownInProgress = false;
-
public:
- GUIDisplayReady(bool suspendInProgress, bool shutdownInProgress)
- : suspendInProgress(suspendInProgress), shutdownInProgress(shutdownInProgress)
+ explicit EinkReady(::gui::Size displaySize) : einkDisplaySize{displaySize}
{}
- [[nodiscard]] auto getSuspendInProgress() const noexcept
+ [[nodiscard]] auto getDisplaySize() const noexcept -> ::gui::Size
{
- return suspendInProgress;
+ return einkDisplaySize;
}
- [[nodiscard]] auto getShutdownInProgress() const noexcept
- {
- return shutdownInProgress;
- }
+ private:
+ ::gui::Size einkDisplaySize;
};
} // namespace service::gui
M module-services/service-gui/messages/GUIMessage.hpp => module-services/service-gui/messages/GUIMessage.hpp +4 -5
@@ 7,13 7,12 @@
#include <Service/Message.hpp>
#include <core/DrawCommand.hpp>
-namespace sgui
+namespace service::gui
{
-
class GUIMessage : public sys::DataMessage
{
public:
- GUIMessage() : sys::DataMessage(MessageType::GUIMessage){};
+ GUIMessage() : sys::DataMessage(MessageType::GUIMessage)
+ {}
};
-
-} /* namespace sgui */
+} // namespace service::gui
M module-services/service-gui/messages/RenderingFinished.hpp => module-services/service-gui/messages/RenderingFinished.hpp +23 -2
@@ 5,8 5,29 @@
#include "GUIMessage.hpp"
+#include <gui/Common.hpp>
+
namespace service::gui
{
- class RenderingFinished : public sgui::GUIMessage
- {};
+ class RenderingFinished : public GUIMessage
+ {
+ public:
+ RenderingFinished(int contextId, ::gui::RefreshModes refreshMode)
+ : contextId{contextId}, refreshMode{refreshMode}
+ {}
+
+ [[nodiscard]] int getContextId() const noexcept
+ {
+ return contextId;
+ }
+
+ [[nodiscard]] ::gui::RefreshModes getRefreshMode() const noexcept
+ {
+ return refreshMode;
+ }
+
+ private:
+ int contextId;
+ ::gui::RefreshModes refreshMode;
+ };
} // namespace service::gui
A module-services/service-gui/tests/CMakeLists.txt => module-services/service-gui/tests/CMakeLists.txt +21 -0
@@ 0,0 1,21 @@
+add_catch2_executable(
+ NAME
+ context-pool-tests
+ SRCS
+ tests-main.cpp
+ test-ContextPool.cpp
+ MockedSynchronizationMechanism.hpp
+ LIBS
+ service-gui
+)
+
+add_catch2_executable(
+ NAME
+ commands-queue-tests
+ SRCS
+ tests-main.cpp
+ test-DrawCommandsQueue.cpp
+ MockedSynchronizationMechanism.hpp
+ LIBS
+ service-gui
+)
A module-services/service-gui/tests/MockedSynchronizationMechanism.hpp => module-services/service-gui/tests/MockedSynchronizationMechanism.hpp +31 -0
@@ 0,0 1,31 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <SynchronizationMechanism.hpp>
+
+#include "mutex.hpp"
+
+#include <chrono>
+#include <thread>
+
+class MockedSynchronizationMechanism : public service::gui::SynchronizationMechanism
+{
+ public:
+ void wait(cpp_freertos::Mutex &lock, std::chrono::milliseconds timeout, Predicate predicate) override;
+ void notify() override;
+};
+
+void MockedSynchronizationMechanism::wait(cpp_freertos::Mutex &lock,
+ std::chrono::milliseconds timeout,
+ Predicate predicate)
+{
+ using namespace std::chrono_literals;
+ while (!predicate()) {
+ lock.Unlock();
+ std::this_thread::sleep_for(10ms);
+ lock.Lock();
+ }
+}
+
+void MockedSynchronizationMechanism::notify()
+{}
A module-services/service-gui/tests/test-ContextPool.cpp => module-services/service-gui/tests/test-ContextPool.cpp +53 -0
@@ 0,0 1,53 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <catch2/catch.hpp>
+
+#include "ContextPool.hpp"
+#include "MockedSynchronizationMechanism.hpp"
+
+#include <chrono>
+#include <thread>
+
+using namespace service::gui;
+
+TEST_CASE("ContextPoolTests")
+{
+ const ::gui::Size displaySize{1, 1};
+ constexpr auto ContextPoolCapacity = 1;
+ constexpr auto ContextPoolFirstId = 0;
+ ContextPool contextPool{displaySize, ContextPoolCapacity, std::make_unique<MockedSynchronizationMechanism>()};
+ REQUIRE(!contextPool.isAnyContextLocked());
+
+ SECTION("Borrow context when all are used")
+ {
+ const auto [contextId, context] = contextPool.borrowContext();
+ REQUIRE(contextId == ContextPoolFirstId);
+ REQUIRE(contextPool.isAnyContextLocked());
+
+ std::thread thr{[&contextPool, id = contextId]() {
+ std::this_thread::sleep_for(std::chrono::milliseconds{500});
+ contextPool.returnContext(id);
+ }};
+ const auto [nextContextId, nextContext] = contextPool.borrowContext();
+ REQUIRE(contextId == nextContextId);
+
+ if (thr.joinable()) {
+ thr.join();
+ }
+ contextPool.returnContext(nextContextId);
+ REQUIRE(!contextPool.isAnyContextLocked());
+ }
+
+ SECTION("Return unused context")
+ {
+ REQUIRE(!contextPool.isAnyContextLocked());
+ REQUIRE_NOTHROW(contextPool.returnContext(0));
+ }
+
+ SECTION("Return invalid context id")
+ {
+ REQUIRE(!contextPool.isAnyContextLocked());
+ REQUIRE_NOTHROW(contextPool.returnContext(10));
+ }
+}
A module-services/service-gui/tests/test-DrawCommandsQueue.cpp => module-services/service-gui/tests/test-DrawCommandsQueue.cpp +73 -0
@@ 0,0 1,73 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <catch2/catch.hpp>
+
+#include "DrawCommandsQueue.hpp"
+#include "MockedSynchronizationMechanism.hpp"
+
+#include <chrono>
+#include <thread>
+
+using namespace service::gui;
+
+TEST_CASE("DrawCommandsQueueTests")
+{
+ constexpr auto ExpectedQueueSize = 4U;
+ DrawCommandsQueue queue{ExpectedQueueSize, std::make_unique<MockedSynchronizationMechanism>()};
+
+ SECTION("Enqueue")
+ {
+ queue.enqueue(DrawCommandsQueue::QueueItem{});
+ REQUIRE(queue.size() == 1U);
+ }
+
+ SECTION("Single-thread dequeue")
+ {
+ queue.enqueue(DrawCommandsQueue::QueueItem{});
+ REQUIRE(queue.size() == 1U);
+
+ const auto item = queue.dequeue();
+ REQUIRE(item.commands.empty());
+ REQUIRE(item.refreshMode == ::gui::RefreshModes::GUI_REFRESH_FAST);
+ REQUIRE(queue.size() == 0);
+ }
+
+ SECTION("Multi-thread dequeue")
+ {
+ std::thread thr{[&queue]() {
+ std::this_thread::sleep_for(std::chrono::milliseconds{100});
+ queue.enqueue(DrawCommandsQueue::QueueItem{});
+ }};
+
+ const auto item = queue.dequeue();
+ if (thr.joinable()) {
+ thr.join();
+ }
+
+ REQUIRE(item.commands.empty());
+ REQUIRE(item.refreshMode == ::gui::RefreshModes::GUI_REFRESH_FAST);
+ REQUIRE(queue.size() == 0U);
+ }
+
+ SECTION("Clear")
+ {
+ queue.enqueue(DrawCommandsQueue::QueueItem{});
+ queue.enqueue(DrawCommandsQueue::QueueItem{});
+ REQUIRE(queue.size() == 2U);
+
+ queue.clear();
+ REQUIRE(queue.size() == 0);
+ }
+
+ SECTION("Clear and get max refresh mode")
+ {
+ queue.enqueue(DrawCommandsQueue::QueueItem{});
+ queue.enqueue(DrawCommandsQueue::QueueItem{{}, ::gui::RefreshModes::GUI_REFRESH_DEEP});
+ REQUIRE(queue.size() == 2U);
+
+ const auto maxRefreshMode = queue.getMaxRefreshModeAndClear();
+ REQUIRE(queue.size() == 0);
+ REQUIRE(maxRefreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP);
+ }
+}
A module-services/service-gui/tests/tests-main.cpp => module-services/service-gui/tests/tests-main.cpp +5 -0
@@ 0,0 1,5 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
+#include <catch2/catch.hpp>