InfiniSim/main.cpp
Jean-François Milants 8ae5ba7c0a Integrate the new heap implementation from InfiniTime (https://github.com/InfiniTimeOrg/InfiniTime/pull/1709).
Since FreeRTOS.h, portmacro_cmsis.h and task.h are now built in C (by lv_mem.c), I had to change some includes and declarations to make them compatible with a C compiler.

Integrating the new memory management from InfiniTime in InfiniSim is not easy because InfiniSim does not include the whole FreeRTOS. Which means that, for example, pvPortMalloc() and vPortFree() are not accessible from InfiniSim.
As a first step, I provided custom implementations for pvPortMalloc(), vPortFree() which are based on ... malloc(). These function keep track of the memory that is currently allocated so that xPortGetFreeHeapSize(), xPortGetMinimumEverFreeHeapSize() return something.

Not that this implementation do not keep track of all the memory allocations done in InfiniTime. It can only "see" those done via pvPortMalloc(). It means that the available memory displayed by InfiniSim will probably be very optimistic.
2023-05-03 22:39:40 +02:00

1086 lines
42 KiB
C++

/**
* @file main
*
*/
/*********************
* INCLUDES
*********************/
#define _DEFAULT_SOURCE /* needed for usleep() */
#include <stdlib.h>
#include <unistd.h>
#define SDL_MAIN_HANDLED /*To fix SDL's "undefined reference to WinMain" issue*/
#include <SDL2/SDL.h>
#include "lvgl/lvgl.h"
//#include "lvgl/examples/lv_examples.h"
//#include "lv_demos/lv_demo.h"
#include "lv_drivers/display/monitor.h"
#include "lv_drivers/indev/mouse.h"
#include "lv_drivers/indev/keyboard.h"
#include "lv_drivers/indev/mousewheel.h"
// get PineTime header
#include "displayapp/InfiniTimeTheme.h"
#include <drivers/Hrs3300.h>
#include <drivers/Bma421.h>
#include "BootloaderVersion.h"
#include "components/battery/BatteryController.h"
#include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h"
#include "components/brightness/BrightnessController.h"
#include "components/motor/MotorController.h"
#include "components/datetime/DateTimeController.h"
#include "components/heartrate/HeartRateController.h"
#include "components/fs/FS.h"
#include "drivers/Spi.h"
#include "drivers/SpiMaster.h"
#include "drivers/SpiNorFlash.h"
#include "drivers/St7789.h"
#include "drivers/TwiMaster.h"
#include "drivers/Cst816s.h"
#include "drivers/PinMap.h"
#include "systemtask/SystemTask.h"
#include "drivers/PinMap.h"
#include "touchhandler/TouchHandler.h"
#include "buttonhandler/ButtonHandler.h"
// get the simulator-headers
#include "displayapp/DisplayApp.h"
#include "displayapp/LittleVgl.h"
#include <nrfx_gpiote.h>
#include <hal/nrf_gpio.h>
#include <mdk/nrf52.h> // initialize NRF_WDT and NRF_POWER
#include <iostream>
#include <typeinfo>
#include <algorithm>
#include <cmath> // std::pow
// additional includes for 'saveScreenshot()' function
#include <iomanip> // put_time
#include <sstream>
#include <chrono>
#include <ctime> // localtime
#if defined(WITH_PNG)
#include <libpng/png.h>
#endif
#include <gif.h>
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
// copied from lv_drivers/display/monitor.c to get the SDL_Window for the InfiniTime screen
extern "C"
{
typedef struct {
SDL_Window * window;
SDL_Renderer * renderer;
SDL_Texture * texture;
volatile bool sdl_refr_qry;
#if MONITOR_DOUBLE_BUFFERED
uint32_t * tft_fb_act;
#else
uint32_t tft_fb[LV_HOR_RES_MAX * LV_VER_RES_MAX];
#endif
}monitor_t;
extern monitor_t monitor;
}
void saveScreenshot()
{
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
// timestamped png filename
std::stringstream ss;
ss << "InfiniSim_" << std::put_time(std::localtime(&in_time_t), "%F_%H%M%S");
std::string screenshot_filename_base = ss.str();
// TODO: use std::format once we have C++20 and new enough GCC 13
//std::string screenshot_filename_base = std::format("InfiniSim_%F_%H%M%S", std::chrono::floor<std::chrono::seconds>(now));
//std::string screenshot_filename_base = "InfiniSim";
const int width = 240;
const int height = 240;
auto renderer = monitor.renderer;
#if defined(WITH_PNG)
std::string screenshot_filename = screenshot_filename_base + ".png";
FILE * fp2 = fopen(screenshot_filename.c_str(), "wb");
if (!fp2) {
// dealing with error
return;
}
// 1. Create png struct pointer
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
if (!png_ptr){
// dealing with error
}
png_infop info_ptr = png_create_info_struct(png_ptr);
if (!info_ptr) {
png_destroy_write_struct(&png_ptr, (png_infopp)NULL);
// dealing with error
}
int bit_depth = 8;
png_init_io(png_ptr, fp2);
png_set_IHDR(png_ptr, info_ptr, width, height, bit_depth, \
PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, \
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
// 3. Convert 1d array to 2d array to be suitable for png struct
// I assumed the original array is 1d
std::array<png_bytep, 240> row_pointers;
//png_bytepp row_pointers = (png_bytepp)png_malloc(png_ptr, sizeof(png_bytep) * height);
for (int i = 0; i < height; i++) {
row_pointers[i] = (png_bytep)png_malloc(png_ptr, width*4);
}
constexpr size_t zoom = MONITOR_ZOOM;
const Uint32 format = SDL_PIXELFORMAT_RGBA8888;
SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, width*zoom, height*zoom, 32, format);
SDL_RenderReadPixels(renderer, NULL, format, surface->pixels, surface->pitch);
png_bytep pixels = (png_bytep)surface->pixels;
for (int hi = 0; hi < height; hi++) {
for (int wi = 0; wi < width; wi++) {
int c = wi * 4;
row_pointers.at(hi)[wi*4+0] = pixels[hi*surface->pitch*zoom + wi*4*zoom + 3]; // red
row_pointers.at(hi)[wi*4+1] = pixels[hi*surface->pitch*zoom + wi*4*zoom + 2]; // greeen
row_pointers.at(hi)[wi*4+2] = pixels[hi*surface->pitch*zoom + wi*4*zoom + 1]; // blue
row_pointers.at(hi)[wi*4+3] = 255; // alpha
}
}
// 4. Write png file
png_write_info(png_ptr, info_ptr);
png_write_image(png_ptr, row_pointers.data());
png_write_end(png_ptr, info_ptr);
png_destroy_write_struct(&png_ptr, &info_ptr);
fclose(fp2);
SDL_FreeSurface(surface);
#else
std::string screenshot_filename = screenshot_filename_base + ".bmp";
const Uint32 format = SDL_PIXELFORMAT_RGB888;
SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, 24, format);
SDL_RenderReadPixels(renderer, NULL, format, surface->pixels, surface->pitch);
SDL_SaveBMP(surface, screenshot_filename.c_str());
SDL_FreeSurface(surface);
#endif
std::cout << "InfiniSim: Screenshot created: " << screenshot_filename << std::endl;
}
class GifManager
{
private:
GifWriter writer = {};
std::chrono::system_clock::time_point last_frame;
bool in_progress = false;
static constexpr uint32_t delay_ds = 100/20; // in 1/100 s, so 1 ds = 10 ms
static constexpr int sdl_width = 240;
static constexpr int sdl_height = 240;
public:
GifManager()
{}
~GifManager()
{
if (in_progress) {
close();
}
}
bool is_in_progress() const
{
return in_progress;
}
void create_new()
{
assert(!in_progress);
auto now = std::chrono::system_clock::now();
auto in_time_t = std::chrono::system_clock::to_time_t(now);
// timestamped png filename
std::stringstream ss;
ss << "InfiniSim_" << std::put_time(std::localtime(&in_time_t), "%F_%H%M%S");
std::string screenshot_filename_base = ss.str();
// TODO: use std::format once we have C++20 and new enough GCC 13
//std::string screenshot_filename_base = std::format("InfiniSim_%F_%H%M%S", std::chrono::floor<std::chrono::seconds>(now));
std::string screenshot_filename = screenshot_filename_base + ".gif";
std::cout << "InfiniSim: Screen-capture started: " << screenshot_filename << std::endl;
GifBegin( &writer, screenshot_filename.c_str(), sdl_width, sdl_height, delay_ds, 8, true );
in_progress = true;
write_frame(true);
}
void write_frame(bool force = false)
{
assert(in_progress);
auto now = std::chrono::system_clock::now();
if (force || ((now - last_frame) > std::chrono::milliseconds(delay_ds*10)) )
{
last_frame = std::chrono::system_clock::now();
auto renderer = monitor.renderer;
constexpr size_t zoom = MONITOR_ZOOM;
const Uint32 format = SDL_PIXELFORMAT_RGBA8888;
SDL_Surface *surface = SDL_CreateRGBSurfaceWithFormat(0, sdl_width*zoom, sdl_height*zoom, 32, format);
SDL_RenderReadPixels(renderer, NULL, format, surface->pixels, surface->pitch);
uint8_t *pixels = (uint8_t*) surface->pixels;
std::array<uint8_t, 240*240*4> image;
for (int hi = 0; hi < sdl_height; hi++) {
for (int wi = 0; wi < sdl_width; wi++) {
auto red = pixels[hi*surface->pitch*zoom + wi*4*zoom + 3]; // red
auto green = pixels[hi*surface->pitch*zoom + wi*4*zoom + 2]; // green
auto blue = pixels[hi*surface->pitch*zoom + wi*4*zoom + 1]; // blue
image[(hi * sdl_width + wi)*4 + 0] = red;
image[(hi * sdl_width + wi)*4 + 1] = green;
image[(hi * sdl_width + wi)*4 + 2] = blue;
image[(hi * sdl_width + wi)*4 + 3] = 255; // no alpha
}
}
GifWriteFrame(&writer, image.data(), sdl_width, sdl_height, delay_ds, 8, false);
}
}
void close()
{
assert(in_progress);
in_progress = false;
GifEnd(&writer);
std::cout << "InfiniSim: Screen-capture finished" << std::endl;
}
};
/**********************
* STATIC PROTOTYPES
**********************/
static void hal_init(void);
static int tick_thread(void *data);
/**********************
* STATIC VARIABLES
**********************/
lv_indev_t *kb_indev;
lv_indev_t *mouse_indev = nullptr;
/**********************
* MACROS
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
void nrfx_gpiote_evt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) {}
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* VARIABLES
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
/**********************
* GLOBAL FUNCTIONS
**********************/
constexpr NRF_TWIM_Type *NRF_TWIM1 = nullptr;
static constexpr uint8_t touchPanelTwiAddress = 0x15;
static constexpr uint8_t motionSensorTwiAddress = 0x18;
static constexpr uint8_t heartRateSensorTwiAddress = 0x44;
Pinetime::Drivers::SpiMaster spi {Pinetime::Drivers::SpiMaster::SpiModule::SPI0,
{Pinetime::Drivers::SpiMaster::BitOrder::Msb_Lsb,
Pinetime::Drivers::SpiMaster::Modes::Mode3,
Pinetime::Drivers::SpiMaster::Frequencies::Freq8Mhz,
Pinetime::PinMap::SpiSck,
Pinetime::PinMap::SpiMosi,
Pinetime::PinMap::SpiMiso}};
Pinetime::Drivers::Spi lcdSpi {spi, Pinetime::PinMap::SpiLcdCsn};
Pinetime::Drivers::St7789 lcd {lcdSpi, Pinetime::PinMap::LcdDataCommand};
Pinetime::Drivers::Spi flashSpi {spi, Pinetime::PinMap::SpiFlashCsn};
Pinetime::Drivers::SpiNorFlash spiNorFlash {"spiNorFlash.raw"};
// The TWI device should work @ up to 400Khz but there is a HW bug which prevent it from
// respecting correct timings. According to erratas heet, this magic value makes it run
// at ~390Khz with correct timings.
static constexpr uint32_t MaxTwiFrequencyWithoutHardwareBug {0x06200000};
Pinetime::Drivers::TwiMaster twiMaster {NRF_TWIM1, MaxTwiFrequencyWithoutHardwareBug, Pinetime::PinMap::TwiSda, Pinetime::PinMap::TwiScl};
Pinetime::Drivers::Cst816S touchPanel; // {twiMaster, touchPanelTwiAddress};
//#ifdef PINETIME_IS_RECOVERY
// #include "displayapp/DummyLittleVgl.h"
// #include "displayapp/DisplayAppRecovery.h"
//#else
// #include "displayapp/LittleVgl.h"
// #include "displayapp/DisplayApp.h"
//#endif
Pinetime::Drivers::Bma421 motionSensor {twiMaster, motionSensorTwiAddress};
Pinetime::Drivers::Hrs3300 heartRateSensor {twiMaster, heartRateSensorTwiAddress};
TimerHandle_t debounceTimer;
TimerHandle_t debounceChargeTimer;
Pinetime::Controllers::Battery batteryController;
Pinetime::Controllers::Ble bleController;
Pinetime::Controllers::HeartRateController heartRateController;
Pinetime::Applications::HeartRateTask heartRateApp(heartRateSensor, heartRateController);
Pinetime::Controllers::FS fs {spiNorFlash};
Pinetime::Controllers::Settings settingsController {fs};
Pinetime::Controllers::MotorController motorController {};
Pinetime::Controllers::DateTime dateTimeController {settingsController};
Pinetime::Drivers::Watchdog watchdog;
Pinetime::Controllers::NotificationManager notificationManager;
Pinetime::Controllers::MotionController motionController;
#if defined(INFINITIME_TIMERCONTROLLER)
Pinetime::Controllers::TimerController timerController;
#endif
Pinetime::Controllers::AlarmController alarmController {dateTimeController};
Pinetime::Controllers::TouchHandler touchHandler;
Pinetime::Controllers::ButtonHandler buttonHandler;
Pinetime::Controllers::BrightnessController brightnessController {};
Pinetime::Applications::DisplayApp displayApp(lcd,
touchPanel,
batteryController,
bleController,
dateTimeController,
watchdog,
notificationManager,
heartRateController,
settingsController,
motorController,
motionController,
#if defined(INFINITIME_TIMERCONTROLLER)
timerController,
#endif
alarmController,
brightnessController,
touchHandler,
fs);
Pinetime::System::SystemTask systemTask(spi,
spiNorFlash,
twiMaster,
touchPanel,
batteryController,
bleController,
dateTimeController,
#if defined(INFINITIME_TIMERCONTROLLER)
timerController,
#endif
alarmController,
watchdog,
notificationManager,
heartRateSensor,
motionController,
motionSensor,
settingsController,
heartRateController,
displayApp,
heartRateApp,
fs,
touchHandler,
buttonHandler);
// variable used in SystemTask.cpp Work loop
std::chrono::time_point<std::chrono::system_clock, std::chrono::nanoseconds> NoInit_BackUpTime;
class Framework {
public:
// Contructor which initialize the parameters.
Framework(bool visible_, int height_, int width_) :
visible(visible_), height(height_), width(width_)
{
if (visible) {
//SDL_Init(SDL_INIT_VIDEO); // Initializing SDL as Video
SDL_CreateWindowAndRenderer(width, height, 0, &window, &renderer);
SDL_SetWindowTitle(window, "LV Simulator Status");
{ // move window a bit to the right, to not be over the PineTime Screen
int x,y;
SDL_GetWindowPosition(window, &x, &y);
SDL_SetWindowPosition(window, x+LV_HOR_RES_MAX, y);
}
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); // setting draw color
SDL_RenderClear(renderer); // Clear the newly created window
SDL_RenderPresent(renderer); // Reflects the changes done in the
// window.
}
init_NRF_WDT();
init_NRF_POWER();
motorController.Init();
settingsController.Init();
printf("initial free_size = %u\n", xPortGetFreeHeapSize());
// update time to current system time once on startup
dateTimeController.SetCurrentTime(std::chrono::system_clock::now());
systemTask.Start();
// initialize the first LVGL screen
//const auto clockface = settingsController.GetClockFace();
//switch_to_screen(1+clockface);
}
// Destructor
~Framework(){
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
}
void draw_circle_red(int center_x, int center_y, int radius_){
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
draw_circle_(center_x, center_y, radius_);
}
void draw_circle_green(int center_x, int center_y, int radius_){
SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
draw_circle_(center_x, center_y, radius_);
}
void draw_circle_blue(int center_x, int center_y, int radius_){
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
draw_circle_(center_x, center_y, radius_);
}
void draw_circle_yellow(int center_x, int center_y, int radius_){
SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255);
draw_circle_(center_x, center_y, radius_);
}
void draw_circle_grey(int center_x, int center_y, int radius_){
SDL_SetRenderDrawColor(renderer, 128, 128, 128, 255);
draw_circle_(center_x, center_y, radius_);
}
void draw_circle_white(int center_x, int center_y, int radius_){
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
draw_circle_(center_x, center_y, radius_);
}
void draw_circle_(int center_x, int center_y, int radius_){
// Drawing circle
for(int x=center_x-radius_; x<=center_x+radius_; x++){
for(int y=center_y-radius_; y<=center_y+radius_; y++){
if((std::pow(center_y-y,2)+std::pow(center_x-x,2)) <=
std::pow(radius_,2)){
SDL_RenderDrawPoint(renderer, x, y);
}
}
}
}
void refresh() {
// always refresh the LVGL screen
this->refresh_screen();
if (!visible) {
return;
}
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
SDL_RenderClear(renderer);
{ // motorController.motor_is_running
constexpr const int center_x = 45;
constexpr const int center_y = 15;
bool motor_is_running = nrf_gpio_pin_read(Pinetime::PinMap::Motor);
if (motor_is_running) {
draw_circle_red(center_x, center_y, 15);
} else {
draw_circle_grey(center_x, center_y, 15);
}
}
{ // ble.motor_is_running
constexpr const int center_x = 75;
constexpr const int center_y = 15;
if (bleController.IsConnected()) {
draw_circle_blue(center_x, center_y, 15);
} else {
draw_circle_grey(center_x, center_y, 15);
}
}
// batteryController.percentRemaining
for (uint8_t percent=0; percent<=10; percent++) {
const int center_x = 15+15*percent;
const int center_y = 60;
if (batteryController.percentRemaining < percent*10) {
draw_circle_grey(center_x, center_y, 15);
} else {
draw_circle_green(center_x, center_y, 15);
}
}
{ // batteryController.isCharging
constexpr const int center_x = 15;
constexpr const int center_y = 90;
if (batteryController.isCharging) {
draw_circle_yellow(center_x, center_y, 15);
} else
{
draw_circle_grey(center_x, center_y, 15);
}
}
{ // brightnessController.Level
constexpr const int center_y = 15;
const Pinetime::Controllers::BrightnessController::Levels level = brightnessController.Level();
uint8_t level_idx = 0;
if (level == Pinetime::Controllers::BrightnessController::Levels::Low)
{
level_idx = 1;
} else if (level == Pinetime::Controllers::BrightnessController::Levels::Medium)
{
level_idx = 2;
} else if (level == Pinetime::Controllers::BrightnessController::Levels::High)
{
level_idx = 3;
}
for (uint8_t i=0; i<4; i++) {
const int center_x = 115+15*i;
if (i <= level_idx) {
draw_circle_white(center_x, center_y, 15);
} else {
draw_circle_grey(center_x, center_y, 15);
}
}
}
// Show the change on the screen
SDL_RenderPresent(renderer);
}
// prepared notficitions, one per message category
const std::vector<std::string> notification_messages {
"0category:\nUnknown",
"Lorem ipsum\ndolor sit amet,\nconsectetur adipiscing elit,\n",
"1SimpleAlert",
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"2Email:",
"Vitae aliquet nec ullamcorper sit amet.",
"3News:",
"Id\naliquet\nrisus\nfeugiat\nin\nante\nmetus\ndictum\nat.",
"4IncomingCall:",
"Ut porttitor leo a diam sollicitudin.",
"5MissedCall:",
"Ultrices tincidunt arcu non sodales neque sodales ut etiam sit.",
"6Sms:",
"Pellentesque dignissim enim sit amet.",
"7VoiceMail:",
"Urna nec tincidunt praesent semper feugiat nibh sed pulvinar proin.",
"8Schedule:",
"Tellus id interdum velit laoreet id donec ultrices tincidunt.",
"9HighProriotyAlert:",
"Viverra maecenas accumsan lacus vel facilisis volutpat est velit egestas.",
"10InstantMessage:",
"Volutpat consequat mauris nunc congue.",
};
size_t notification_idx = 0; // which message to send next
void send_notification() {
Pinetime::Controllers::NotificationManager::Notification notif;
const std::string &title = notification_messages.at(notification_idx*2);
const std::string &message = notification_messages.at(notification_idx*2+1);
std::copy(title.begin(), title.end(), notif.message.data());
notif.message[title.size()] = '\0'; // title and message is \0 separated
std::copy(message.begin(), message.end(), notif.message.data()+title.size()+1);
notif.message[title.size() + 1 + message.size()] = '\0'; // zero terminate the message
notif.size = title.size() + 1 + message.size();
notif.category = static_cast<Pinetime::Controllers::NotificationManager::Categories>(notification_idx % 11);
notificationManager.Push(std::move(notif));
// send next message the next time
notification_idx++;
if (notification_idx >= notification_messages.size()/2) {
notification_idx = 0;
}
if (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::On)
{
if (screen_off_created) {
// wake up! (deletes screen_off label)
systemTask.PushMessage(Pinetime::System::Messages::GoToRunning);
}
displayApp.PushMessage(Pinetime::Applications::Display::Messages::NewNotification);
}
}
// can't use SDL_PollEvent, as those are fed to lvgl
// implement a non-descructive key-pressed handler (as not consuming SDL_Events)
void handle_keys() {
const Uint8 *state = SDL_GetKeyboardState(NULL);
const bool key_shift = state[SDL_SCANCODE_LSHIFT] || state[SDL_SCANCODE_RSHIFT];
auto debounce = [&] (const char key_low, const char key_capital, const bool scancode, bool &key_handled) {
if (scancode && !key_handled) {
if (key_shift) {
this->handle_key(key_capital);
} else {
this->handle_key(key_low);
}
key_handled = true;
} else if (scancode && key_handled) {
// ignore, already handled
} else {
key_handled = false;
}
};
debounce('r', 'R', state[SDL_SCANCODE_R], key_handled_r);
debounce('n', 'N', state[SDL_SCANCODE_N], key_handled_n);
debounce('m', 'M', state[SDL_SCANCODE_M], key_handled_m);
debounce('b', 'B', state[SDL_SCANCODE_B], key_handled_b);
debounce('v', 'V', state[SDL_SCANCODE_V], key_handled_v);
debounce('c', 'C', state[SDL_SCANCODE_C], key_handled_c);
debounce('l', 'L', state[SDL_SCANCODE_L], key_handled_l);
debounce('p', 'P', state[SDL_SCANCODE_P], key_handled_p);
debounce('s', 'S', state[SDL_SCANCODE_S], key_handled_s);
debounce('h', 'H', state[SDL_SCANCODE_H], key_handled_h);
debounce('i', 'I', state[SDL_SCANCODE_I], key_handled_i);
// screen switcher buttons
debounce('1', '!'+1, state[SDL_SCANCODE_1], key_handled_1);
debounce('2', '!'+2, state[SDL_SCANCODE_2], key_handled_2);
debounce('3', '!'+3, state[SDL_SCANCODE_3], key_handled_3);
debounce('4', '!'+4, state[SDL_SCANCODE_4], key_handled_4);
debounce('5', '!'+5, state[SDL_SCANCODE_5], key_handled_5);
debounce('6', '!'+6, state[SDL_SCANCODE_6], key_handled_6);
debounce('7', '!'+7, state[SDL_SCANCODE_7], key_handled_7);
debounce('8', '!'+8, state[SDL_SCANCODE_8], key_handled_8);
debounce('9', '!'+9, state[SDL_SCANCODE_9], key_handled_9);
debounce('0', '!'+0, state[SDL_SCANCODE_0], key_handled_0);
// direction keys
debounce(':', ':', state[SDL_SCANCODE_UP], key_handled_up);
debounce(';', ';', state[SDL_SCANCODE_DOWN], key_handled_down);
debounce('<', '<', state[SDL_SCANCODE_LEFT], key_handled_left);
debounce('>', '>', state[SDL_SCANCODE_RIGHT], key_handled_right);
}
// inject a swipe gesture to the touch handler and notify displayapp to notice it
void send_gesture(Pinetime::Drivers::Cst816S::Gestures gesture)
{
Pinetime::Drivers::Cst816S::TouchInfos info;
info.isValid = true;
info.touching = true;
info.gesture = gesture;
touchHandler.ProcessTouchInfo(info);
displayApp.PushMessage(Pinetime::Applications::Display::Messages::TouchEvent);
info.touching = false;
info.gesture = Pinetime::Drivers::Cst816S::Gestures::None;
touchHandler.ProcessTouchInfo(info);
}
// modify the simulated controller depending on the pressed key
void handle_key(SDL_Keycode key) {
if (key == 'r') {
motorController.StartRinging();
} else if (key == 'R') {
motorController.StopRinging();
} else if (key == 'm') {
motorController.RunForDuration(100);
} else if (key == 'M') {
motorController.RunForDuration(255);
} else if (key == 'n') {
send_notification();
} else if (key == 'N') {
notificationManager.ClearNewNotificationFlag();
} else if (key == 'b') {
bleController.Connect();
} else if (key == 'B') {
bleController.Disconnect();
} else if (key == 'v') {
if (batteryController.percentRemaining >= 90) {
batteryController.percentRemaining = 100;
} else {
batteryController.percentRemaining += 10;
}
} else if (key == 'V') {
if (batteryController.percentRemaining <= 10) {
batteryController.percentRemaining = 0;
} else {
batteryController.percentRemaining -= 10;
}
} else if (key == 'c') {
batteryController.isCharging = true;
batteryController.isPowerPresent = true;
} else if (key == 'C') {
batteryController.isCharging = false;
batteryController.isPowerPresent = false;
} else if (key == 'l' && !screen_off_created) {
brightnessController.Higher();
} else if (key == 'L' && !screen_off_created) {
brightnessController.Lower();
} else if (key == 'p') {
this->print_memory_usage = true;
} else if (key == 'P') {
this->print_memory_usage = false;
} else if (key == 's') {
motionSensor.steps += 500;
} else if (key == 'S') {
if (motionSensor.steps > 500) {
motionSensor.steps -= 500;
} else {
motionSensor.steps = 0;
}
} else if (key == 'h') {
if (heartRateController.State() == Pinetime::Controllers::HeartRateController::States::Stopped) {
heartRateController.Start();
} else if (heartRateController.State() == Pinetime::Controllers::HeartRateController::States::NotEnoughData) {
heartRateController.Update(Pinetime::Controllers::HeartRateController::States::Running, 10);
} else {
uint8_t heartrate = heartRateController.HeartRate();
heartRateController.Update(Pinetime::Controllers::HeartRateController::States::Running, heartrate + 10);
}
} else if (key == 'H') {
heartRateController.Stop();
} else if (key == 'i') {
saveScreenshot();
} else if (key == 'I') {
if (!gif_manager.is_in_progress())
{
gif_manager.create_new();
} else {
gif_manager.close();
}
} else if (key >= '0' && key <= '9') {
this->switch_to_screen(key-'0');
} else if (key >= '!'+0 && key <= '!'+9) {
this->switch_to_screen(key-'!'+10);
} else if (key == ':') { // up
send_gesture(Pinetime::Drivers::Cst816S::Gestures::SlideUp);
} else if (key == ';') { // down
send_gesture(Pinetime::Drivers::Cst816S::Gestures::SlideDown);
} else if (key == '<') { // left
send_gesture(Pinetime::Drivers::Cst816S::Gestures::SlideLeft);
} else if (key == '>') { // up
send_gesture(Pinetime::Drivers::Cst816S::Gestures::SlideRight);
}
batteryController.voltage = batteryController.percentRemaining * 50;
}
void handle_touch_and_button() {
int x, y;
uint32_t buttons = SDL_GetMouseState(&x, &y);
const bool left_click = (buttons & SDL_BUTTON_LMASK) != 0;
const bool right_click = (buttons & SDL_BUTTON_RMASK) != 0;
if (left_click) {
left_release_sent = false;
systemTask.OnTouchEvent();
return;
} else {
if (!left_release_sent) {
left_release_sent = true;
systemTask.OnTouchEvent();
return;
}
}
if (right_click != right_last_state) {
right_last_state =right_click;
systemTask.PushMessage(Pinetime::System::Messages::HandleButtonEvent);
return;
}
}
// helper function to switch between screens
void switch_to_screen(uint8_t screen_idx)
{
if (screen_idx == 1) {
settingsController.SetWatchFace(Pinetime::Applications::WatchFace::Digital);
displayApp.StartApp(Pinetime::Applications::Apps::Clock, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
else if (screen_idx == 2) {
settingsController.SetWatchFace(Pinetime::Applications::WatchFace::Analog);
displayApp.StartApp(Pinetime::Applications::Apps::Clock, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
else if (screen_idx == 3) {
settingsController.SetWatchFace(Pinetime::Applications::WatchFace::PineTimeStyle);
displayApp.StartApp(Pinetime::Applications::Apps::Clock, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
else if (screen_idx == 4) {
displayApp.StartApp(Pinetime::Applications::Apps::Paddle, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
else if (screen_idx == 5) {
displayApp.StartApp(Pinetime::Applications::Apps::Twos, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
else if (screen_idx == 6) {
displayApp.StartApp(Pinetime::Applications::Apps::Metronome, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
else if (screen_idx == 7) {
displayApp.StartApp(Pinetime::Applications::Apps::FirmwareUpdate, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
else if (screen_idx == 8) {
displayApp.StartApp(Pinetime::Applications::Apps::BatteryInfo, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
else if (screen_idx == 9) {
displayApp.StartApp(Pinetime::Applications::Apps::FlashLight, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
else if (screen_idx == 0) {
displayApp.StartApp(Pinetime::Applications::Apps::QuickSettings, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
else if (screen_idx == 11) {
displayApp.StartApp(Pinetime::Applications::Apps::Music, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
else if (screen_idx == 12) {
displayApp.StartApp(Pinetime::Applications::Apps::Paint, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
else if (screen_idx == 13) {
displayApp.StartApp(Pinetime::Applications::Apps::SysInfo, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
else if (screen_idx == 14) {
displayApp.StartApp(Pinetime::Applications::Apps::Steps, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
else if (screen_idx == 15) {
displayApp.StartApp(Pinetime::Applications::Apps::Error, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
else if (screen_idx == 17) {
displayApp.StartApp(Pinetime::Applications::Apps::PassKey, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
}
else {
std::cout << "unhandled screen_idx: " << int(screen_idx) << std::endl;
}
}
static void screen_off_delete_cb(lv_obj_t *obj, lv_event_t event)
{
if (event == LV_EVENT_DELETE) {
auto* fw = static_cast<Framework*>(obj->user_data);
if (obj == fw->screen_off_bg)
{
// on delete make sure to not double free the screen_off objects
fw->screen_off_created = false;
}
}
}
// render the current status of the simulated controller
void refresh_screen() {
const Pinetime::Controllers::BrightnessController::Levels level = brightnessController.Level();
if (level == Pinetime::Controllers::BrightnessController::Levels::Off) {
if (!screen_off_created) {
screen_off_created = true;
screen_off_bg = lv_obj_create(lv_scr_act(), nullptr);
lv_obj_set_size(screen_off_bg, 240, 240);
lv_obj_set_pos(screen_off_bg, 0, 0);
lv_obj_set_style_local_bg_color(screen_off_bg, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
screen_off_bg->user_data = this; // add callback to prevent double free through Alarm Screen
lv_obj_set_event_cb(screen_off_bg, screen_off_delete_cb);
screen_off_label = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(screen_off_label, "Screen is OFF");
lv_obj_align(screen_off_label, nullptr, LV_ALIGN_CENTER, 0, -20);
}
/* Periodically call the lv_task handler.
* It could be done in a timer interrupt or an OS task too.*/
// only call the task handler if the screen is off,
// when the screen is enabled the call is done in the SystemTask class
lv_task_handler();
} else {
if (screen_off_created) {
screen_off_created = false;
lv_obj_del(screen_off_bg);
lv_obj_del(screen_off_label);
}
}
if (print_memory_usage) {
auto currentFreeHeap = xPortGetFreeHeapSize();
auto minimumEverFreeHeap = xPortGetMinimumEverFreeHeapSize();
if (currentFreeHeap != lastFreeHeapSize) {
// 14KiB is the LVGL memory size used in InfiniTime
constexpr uint32_t pinetime_heap_memory = configTOTAL_HEAP_SIZE;
uint32_t mem_used = pinetime_heap_memory - currentFreeHeap;
// The "budget" value shows how much free lvgl memory the PineTime
// would have free and will go negative when more memory is used
// in the simulator than is available on the real hardware.
int32_t budget = configTOTAL_HEAP_SIZE - mem_used;
printf("Mem: %5u used (change: %+5d, peak: %5u) %d budget left\n", mem_used, lastFreeHeapSize - currentFreeHeap, minimumEverFreeHeap, budget);
lastFreeHeapSize = currentFreeHeap;
}
}
if (gif_manager.is_in_progress())
{
gif_manager.write_frame();
}
}
bool print_memory_usage = false;
// variables to create and destroy an lvgl overlay to indicate a turned off screen
bool screen_off_created = false;
lv_obj_t *screen_off_bg;
lv_obj_t *screen_off_label;
private:
bool key_handled_r = false; // r ... enable ringing, R ... disable ringing
bool key_handled_m = false; // m ... let motor run, M ... stop motor
bool key_handled_n = false; // n ... send notification, N ... clear new notification flag
bool key_handled_b = false; // b ... connect Bluetooth, B ... disconnect Bluetooth
bool key_handled_v = false; // battery Voltage and percentage, v ... increase, V ... decrease
bool key_handled_c = false; // c ... charging, C ... not charging
bool key_handled_l = false; // l ... increase brightness level, L ... lower brightness level
bool key_handled_p = false; // p ... enable print memory usage, P ... disable print memory usage
bool key_handled_s = false; // s ... increase step count, S ... decrease step count
bool key_handled_h = false; // h ... set heartrate running, H ... stop heartrate
bool key_handled_i = false; // i ... take screenshot, I ... start/stop Gif screen capture
// numbers from 0 to 9 to switch between screens
bool key_handled_1 = false;
bool key_handled_2 = false;
bool key_handled_3 = false;
bool key_handled_4 = false;
bool key_handled_5 = false;
bool key_handled_6 = false;
bool key_handled_7 = false;
bool key_handled_8 = false;
bool key_handled_9 = false;
bool key_handled_0 = false;
// direction arrows
bool key_handled_up = false; // inject swipe up
bool key_handled_down = false; // inject swipe down
bool key_handled_left = false; // inject swipe left
bool key_handled_right = false; // inject swipe right
bool visible; // show Simulator window
int height; // Height of the window
int width; // Width of the window
SDL_Renderer *renderer = NULL; // Pointer for the renderer
SDL_Window *window = NULL; // Pointer for the window
bool left_release_sent = true; // make sure to send one mouse button release event
bool right_last_state = false; // varable used to send message only on changing state
size_t lastFreeHeapSize = configTOTAL_HEAP_SIZE;
GifManager gif_manager;
};
int mallocFailedCount = 0;
int stackOverflowCount = 0;
int main(int argc, char **argv)
{
// parse arguments
bool fw_status_window_visible = true;
bool arg_help = false;
for (int i=1; i<argc; i++)
{
const std::string arg(argv[i]);
if (arg == "--hide-status")
{
fw_status_window_visible = false;
} else if (arg == "-h" || arg == "--help")
{
arg_help = true;
} else
{
std::cout << "unknown argument '" << arg << "'" << std::endl;
return 1;
}
}
if (arg_help) {
std::cout << "Usage: " << argv[0] << " [options]" << std::endl;
std::cout << "Options:" << std::endl;
std::cout << " -h, --help show this help message and exit" << std::endl;
std::cout << " --hide-status don't show simulator status window, so only lvgl window is open" << std::endl;
return 0;
}
/*Initialize LVGL*/
lv_init();
/*Initialize the HAL (display, input devices, tick) for LVGL*/
hal_init();
fs.Init();
// initialize the core of our Simulator
Framework fw(fw_status_window_visible, 240,240);
while(1) {
fw.handle_keys(); // key event polling
fw.handle_touch_and_button();
fw.refresh();
usleep(LV_DISP_DEF_REFR_PERIOD * 1000);
}
return 0;
}
/**********************
* STATIC FUNCTIONS
**********************/
/**
* Initialize the Hardware Abstraction Layer (HAL) for the LVGL graphics
* library
*/
static void hal_init(void)
{
/* Use the 'monitor' driver which creates window on PC's monitor to simulate a display*/
monitor_init();
/* Tick init.
* You have to call 'lv_tick_inc()' in periodically to inform LittelvGL about
* how much time were elapsed Create an SDL thread to do this*/
SDL_CreateThread(tick_thread, "tick", NULL);
// use pinetime_theme
//lv_theme_t* th = lv_pinetime_theme_init(
// LV_COLOR_WHITE, LV_COLOR_SILVER, 0, &jetbrains_mono_bold_20, &jetbrains_mono_bold_20, &jetbrains_mono_bold_20, &jetbrains_mono_bold_20);
//lv_theme_set_act(th);
///*Create a display buffer*/
//static lv_disp_buf_t disp_buf1;
//static lv_color_t buf1_1[LV_HOR_RES_MAX * 120];
//lv_disp_buf_init(&disp_buf1, buf1_1, NULL, LV_HOR_RES_MAX * 120);
///*Create a display*/
//lv_disp_drv_t disp_drv;
//lv_disp_drv_init(&disp_drv); /*Basic initialization*/
//disp_drv.buffer = &disp_buf1;
//disp_drv.flush_cb = monitor_flush;
//lv_disp_drv_register(&disp_drv);
/* Add the mouse as input device
* Use the 'mouse' driver which reads the PC's mouse*/
//mouse_init();
//static lv_indev_drv_t indev_drv_1;
//lv_indev_drv_init(&indev_drv_1); /*Basic initialization*/
//indev_drv_1.type = LV_INDEV_TYPE_POINTER;
/*This function will be called periodically (by the library) to get the mouse position and state*/
//indev_drv_1.read_cb = mouse_read;
//mouse_indev = lv_indev_drv_register(&indev_drv_1);
/*Add the keyboard as input device.*/
//lv_indev_drv_t kb_drv;
//lv_indev_drv_init(&kb_drv);
//kb_drv.type = LV_INDEV_TYPE_KEYPAD;
//kb_drv.read_cb = keyboard_read;
//lv_indev_drv_register(&kb_drv);
/*Add the mousewheel as input device.*/
//lv_indev_drv_t enc_drv;
//lv_indev_drv_init(&enc_drv);
//enc_drv.type = LV_INDEV_TYPE_ENCODER;
//enc_drv.read_cb = mousewheel_read;
//lv_indev_drv_register(&enc_drv);
/*Set a cursor for the mouse*/
//LV_IMG_DECLARE(mouse_cursor_icon); /*Declare the image file.*/
//lv_obj_t * cursor_obj = lv_img_create(lv_scr_act(), NULL); /*Create an image object for the cursor */
//lv_img_set_src(cursor_obj, &mouse_cursor_icon); /*Set the image source*/
//lv_indev_set_cursor(mouse_indev, cursor_obj); /*Connect the image object to the driver*/
}
/**
* A task to measure the elapsed time for LVGL
* @param data unused
* @return never return
*/
static int tick_thread(void *data) {
(void)data;
while(1) {
SDL_Delay(LV_DISP_DEF_REFR_PERIOD);
lv_tick_inc(LV_DISP_DEF_REFR_PERIOD); /*Tell LittelvGL that 30 milliseconds were elapsed*/
}
return 0;
}