// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "ServiceEink.hpp"
#include "messages/EinkModeMessage.hpp"
#include <time/ScopedTime.hpp>
#include <log/log.hpp>
#include <messages/EinkMessage.hpp>
#include <messages/ImageMessage.hpp>
#include <MessageType.hpp>
#include <Service/Bus.hpp>
#include <service-gui/messages/GUIMessage.hpp>
#include <vfs.hpp>
#include <cstdio>
#include <cstring>
#include <memory>
enum class EinkWorkerCommands
{
Initialize,
Initialized,
Destroy,
CopyImage,
CopyCompleteCallback,
CopyComplete
};
ServiceEink::ServiceEink(const std::string &name, std::string parent)
: sys::Service(name, parent, 4096 + 1024), selfRefereshTriggerCount{0}, temperatureMeasurementTriggerCount{0},
powerOffTriggerCount{0}, powerOffTimer("PwrOffTimer", this, 3000, sys::Timer::Type::SingleShot)
{
// initialize initial eink parameters
memset(&waveformSettings, 0, sizeof(EinkWaveFormSettings_t));
waveformSettings.mode = EinkWaveformGC16;
waveformSettings.temperature = -1000;
connect(typeid(seink::EinkModeMessage),
[this](sys::DataMessage *message, sys::ResponseMessage *) -> sys::Message_t {
auto msg = static_cast<seink::EinkModeMessage *>(message);
this->displayMode = msg->getMode() == seink::EinkModeMessage::Mode::Normal
? EinkDisplayColorMode_e::EinkDisplayColorModeStandard
: EinkDisplayColorMode_e::EinkDisplayColorModeInverted;
return sys::Message_t();
});
}
ServiceEink::~ServiceEink()
{
// LOG_INFO("[ServiceEink] Cleaning resources");
// release data from settings
if (waveformSettings.LUTCData != nullptr)
delete[] waveformSettings.LUTCData;
if (waveformSettings.LUTDData != nullptr)
delete[] waveformSettings.LUTDData;
// set sure that any new temperature will cause to load data from file.
waveformSettings.temperature = -1000;
}
// Invoked upon receiving data message
sys::Message_t ServiceEink::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)
{
seink::EinkMessage *msg = static_cast<seink::EinkMessage *>(msgl);
switch (msg->messageType) {
case MessageType::EinkImageData: {
powerOffTimer.stop();
auto dmsg = static_cast<seink::ImageMessage *>(msgl);
// LOG_INFO("[%s] EinkImageData", GetName().c_str());
memcpy(einkRenderBuffer, dmsg->getData(), dmsg->getSize());
deepRefresh = dmsg->getDeepRefresh();
shutdownInProgress = dmsg->getShutdown();
if (shutdownInProgress)
LOG_DEBUG("Shutdown In Progress");
suspendInProgress = dmsg->getSuspend();
if (suspendInProgress)
LOG_DEBUG("Suspend In Progress");
auto msg = std::make_shared<sgui::GUIMessage>(MessageType::EinkDMATransfer);
sys::Bus::SendUnicast(msg, this->GetName(), this);
} break;
case MessageType::EinkDMATransfer: {
utils::time::Scoped scopedtimming("EinkDMATransfer");
if (suspended) {
if (suspendInProgress) {
LOG_ERROR("drawing before suspend failed");
suspendInProgress = false;
}
LOG_INFO("[ServiceEink] Received image while suspended, ignoring");
}
else {
EinkPowerOn();
int32_t temperature = EinkGetTemperatureInternal();
// LOG_INFO("temperature: %d", temperature );
EinkStatus_e ret;
if (deepRefresh) {
changeWaveform(EinkWaveforms_e::EinkWaveformGC16, temperature);
EinkDitherDisplay();
}
else {
changeWaveform(EinkWaveforms_e::EinkWaveformDU2, temperature);
}
ret = EinkUpdateFrame(0, 0, 480, 600, einkRenderBuffer, Eink4Bpp, displayMode);
if (ret != EinkOK)
LOG_FATAL("Failed to update frame");
if (deepRefresh) {
// LOG_INFO("EinkDisplayTimingsDeepCleanMode");
ret = EinkRefreshImage(0, 0, 480, 600, EinkDisplayTimingsDeepCleanMode);
}
else {
// LOG_INFO("EinkDisplayTimingsFastRefreshMode");
ret = EinkRefreshImage(0, 0, 480, 600, EinkDisplayTimingsFastRefreshMode);
}
if (ret != EinkOK)
LOG_FATAL("Failed to refresh frame");
powerOffTimer.reload();
auto msg =
std::make_shared<sgui::GUIMessage>(MessageType::GUIDisplayReady, suspendInProgress, shutdownInProgress);
suspendInProgress = false;
shutdownInProgress = false;
sys::Bus::SendUnicast(msg, "ServiceGUI", this);
}
} break;
case MessageType::EinkTemperatureUpdate: {
// LOG_INFO("[%s] EinkTemperatureUpdate", GetName().c_str());
} break;
case MessageType::EinkStateRequest: {
// LOG_INFO("[%s] EinkStateRequest", GetName().c_str());
auto msg = std::make_shared<sgui::GUIMessage>(MessageType::GUIDisplayReady);
sys::Bus::SendUnicast(msg, "ServiceGUI", this);
} break;
default:
break;
};
return std::make_shared<sys::ResponseMessage>();
}
// Invoked during initialization
sys::ReturnCodes ServiceEink::InitHandler()
{
LOG_INFO("[ServiceEink] Initializing");
EinkStatus_e einkStatus = EinkResetAndInitialize();
if (einkStatus != EinkOK) {
LOG_FATAL("Error: Could not initialize Eink display!\n");
}
// TODO remove screen clearing code below.
EinkPowerOn();
// uint8_t s_einkAmbientTemperature = EinkGetTemperatureInternal();
// LOG_INFO("EInk measured temperature: %d\u00B0C", s_einkAmbientTemperature);
//
// // Make the saturation to the lower limit
// if (s_einkAmbientTemperature < 0)
// s_einkAmbientTemperature = 0;
//
// // Make the saturation to the higher limit
// if (s_einkAmbientTemperature > 49)
// s_einkAmbientTemperature = 49;
//
// // Clear the temperature timer count
// deepClearScreen( s_einkAmbientTemperature );
// EinkPowerOff();
//
// //TODO remove and add timer or turning off eink
// EinkPowerOn();
auto msg = std::make_shared<sgui::GUIMessage>(MessageType::GUIDisplayReady);
sys::Bus::SendUnicast(msg, "ServiceGUI", this);
return sys::ReturnCodes::Success;
}
sys::ReturnCodes ServiceEink::DeinitHandler()
{
EinkPowerDown();
return sys::ReturnCodes::Success;
}
sys::ReturnCodes ServiceEink::SwitchPowerModeHandler(const sys::ServicePowerMode mode)
{
LOG_FATAL("[ServiceEink] PowerModeHandler: %s", c_str(mode));
switch (mode) {
case sys::ServicePowerMode ::Active: {
suspended = false;
EinkStatus_e einkStatus = EinkResetAndInitialize();
if (einkStatus != EinkOK) {
LOG_FATAL("Error: Could not initialize Eink display!\n");
}
// TODO remove screen clearing code below.
EinkPowerOn();
// int32_t temperature = EinkGetTemperatureInternal();
// // LOG_INFO("temperature: %d", temperature );
//
// changeWaveform(EinkWaveforms_e::EinkWaveformGC16, temperature);
// EinkDitherDisplay();
//
// EinkRefreshImage (0, 0, 480, 600, EinkDisplayTimingsDeepCleanMode );
EinkPowerOff();
//
// uint8_t s_einkAmbientTemperature = EinkGetTemperatureInternal();
// // Make the saturation to the lower limit
// if (s_einkAmbientTemperature < 0)
// s_einkAmbientTemperature = 0;
//
// // Make the saturation to the higher limit
// if (s_einkAmbientTemperature > 49)
// s_einkAmbientTemperature = 49;
//
// // Clear the temperature timer count
// deepClearScreen( s_einkAmbientTemperature );
} break;
case sys::ServicePowerMode ::SuspendToRAM:
case sys::ServicePowerMode ::SuspendToNVM:
suspended = true;
powerOffTimer.stop();
EinkPowerDown();
break;
}
return sys::ReturnCodes::Success;
}
bool ServiceEink::changeWaveform(EinkWaveforms_e mode, const int32_t temperature)
{
// If neither the temperature nor the waveform has changed - do nothing
if ((temperature == waveformSettings.temperature) && (mode == waveformSettings.mode)) {
return EinkOK;
}
const uint32_t LUTD_SIZE = 16385;
const uint32_t LUTC_SIZE = 64;
const uint32_t LUTR_SIZE = 256; ///< Needed due to \ref EINK_LUTS_FILE_PATH structure
const uint32_t LUTS_TOTAL_SIZE = LUTD_SIZE + LUTC_SIZE + LUTR_SIZE;
waveformSettings.temperature = temperature;
waveformSettings.mode = mode;
unsigned int segment = 0;
if (temperature < 38) {
segment = temperature / 3;
}
else {
if (temperature < 43) {
segment = 12;
}
else {
segment = 13;
}
}
uint32_t offset = 0;
switch (mode) {
case EinkWaveformINIT:
offset = LUTS_TOTAL_SIZE * segment;
break;
case EinkWaveformA2:
offset = LUTS_TOTAL_SIZE * (14 + segment);
break;
case EinkWaveformDU2:
offset = LUTS_TOTAL_SIZE * (28 + segment);
break;
case EinkWaveformGLD16:
offset = LUTS_TOTAL_SIZE * (42 + segment);
break;
case EinkWaveformGC16:
default:
offset = LUTS_TOTAL_SIZE * (56 + segment);
break;
}
// Open file
auto file = vfs.fopen("Luts.bin", "rb");
if (file == nullptr) {
LOG_FATAL("Could not find the LUTS.bin file. Returning");
return false;
}
// Allocate memory for the LUTD data. +1 for the EinkLUTD command for SPI transfer
if (waveformSettings.LUTDData != nullptr)
delete[] waveformSettings.LUTDData;
waveformSettings.LUTDSize = 0;
waveformSettings.LUTDData = new uint8_t[LUTD_SIZE + 1];
if (waveformSettings.LUTDData == nullptr) {
LOG_ERROR("Could not allocate memory for the LUTD array");
vfs.fclose(file);
return false;
}
// Allocate memory for the LUTC data. +1 for the EinkLUTC command for SPI transfer
if (waveformSettings.LUTCData != nullptr)
delete[] waveformSettings.LUTCData;
waveformSettings.LUTCSize = LUTC_SIZE;
waveformSettings.LUTCData = new uint8_t[LUTC_SIZE + 1];
if (waveformSettings.LUTCData == nullptr) {
LOG_ERROR("Could not allocate memory for the LUTC array");
vfs.fclose(file);
return false;
}
waveformSettings.LUTDData[0] = EinkLUTD;
waveformSettings.LUTCData[0] = EinkLUTC;
/// LUTD
vfs.fseek(file, offset, SEEK_SET);
vfs.fread(&waveformSettings.LUTDData[1], 1, LUTD_SIZE, file);
uint8_t frameCount = waveformSettings.LUTDData[1] + 1; // 0x00 - 1 frame, ... , 0x0F - 16 frames
waveformSettings.LUTDSize =
frameCount * 64 + 1 +
1; // (frameCount * 64) - size of actual LUT; (+1) - the byte containing frameCount; (+1) - EinkLUTD command
// LUTC
offset += LUTD_SIZE;
vfs.fseek(file, offset, SEEK_SET);
vfs.fread(&waveformSettings.LUTCData[1], 1, LUTC_SIZE, file);
vfs.fclose(file);
EinkUpdateWaveform(&waveformSettings);
return true;
}
bool ServiceEink::deepClearScreen(int8_t temperature)
{
EinkWaveforms_e wv = waveformSettings.mode;
EinkPowerOn();
changeWaveform(EinkWaveforms_e::EinkWaveformA2, temperature);
EinkStatus_e ret;
memset(einkRenderBuffer, 15, 480 * 600);
ret = EinkUpdateFrame(0, 0, 480, 600, einkRenderBuffer, Eink4Bpp, displayMode);
if (ret != EinkOK)
LOG_FATAL("Failed to update frame");
ret = EinkRefreshImage(0, 0, 480, 600, EinkDisplayTimingsFastRefreshMode);
if (ret != EinkOK)
LOG_FATAL("Failed to refresh frame");
for (uint32_t i = 0; i < 2; i++) {
memset(einkRenderBuffer, 0, 480 * 600);
ret = EinkUpdateFrame(0, 0, 480, 600, einkRenderBuffer, Eink4Bpp, displayMode);
if (ret != EinkOK)
LOG_FATAL("Failed to update frame");
ret = EinkRefreshImage(0, 0, 480, 600, EinkDisplayTimingsFastRefreshMode);
if (ret != EinkOK)
LOG_FATAL("Failed to refresh frame");
memset(einkRenderBuffer, 15, 480 * 600);
ret = EinkUpdateFrame(0, 0, 480, 600, einkRenderBuffer, Eink4Bpp, displayMode);
if (ret != EinkOK)
LOG_FATAL("Failed to update frame");
ret = EinkRefreshImage(0, 0, 480, 600, EinkDisplayTimingsFastRefreshMode);
if (ret != EinkOK)
LOG_FATAL("Failed to refresh frame");
}
changeWaveform(wv, temperature);
EinkPowerOff();
return true;
}