Merge branch 'main' into button-unlock

This commit is contained in:
Tomas Groth Christensen 2023-11-22 14:49:54 +01:00
commit 99d16861a8
43 changed files with 836 additions and 238 deletions

View file

@ -11,6 +11,7 @@ RUN apt-get update -qq \
make \ make \
python3 \ python3 \
python3-pip \ python3-pip \
python3-pil \
tar \ tar \
unzip \ unzip \
wget \ wget \

View file

@ -31,6 +31,10 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- name: Install resource build dependencies
run: |
apt-get update
apt-get -y install --no-install-recommends python3-pil
- name: Build - name: Build
shell: bash shell: bash
run: /opt/build.sh all run: /opt/build.sh all
@ -61,10 +65,10 @@ jobs:
build-simulator: build-simulator:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- name: Install SDL2 development package - name: Install SDL2 and libpng development package
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get -y install libsdl2-dev sudo apt-get -y install libsdl2-dev libpng-dev
- name: Install Ninja - name: Install Ninja
run: | run: |
@ -82,7 +86,7 @@ jobs:
- name: Get InfiniSim repo - name: Get InfiniSim repo
run: | run: |
git clone https://github.com/InfiniTimeOrg/InfiniSim.git --depth 1 --branch main git clone https://github.com/InfiniTimeOrg/InfiniSim.git --depth 1 --branch main
git -C InfiniSim submodule update --init lv_drivers libpng git -C InfiniSim submodule update --init lv_drivers
- name: CMake - name: CMake
# disable BUILD_RESOURCES as this is already done when building the firmware # disable BUILD_RESOURCES as this is already done when building the firmware

View file

@ -5,7 +5,7 @@ set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose Debug or Release")
project(pinetime VERSION 1.13.0 LANGUAGES C CXX ASM) project(pinetime VERSION 1.13.0 LANGUAGES C CXX ASM)
set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD 20)
# set(CMAKE_GENERATOR "Unix Makefiles") # set(CMAKE_GENERATOR "Unix Makefiles")
set(CMAKE_C_EXTENSIONS OFF) set(CMAKE_C_EXTENSIONS OFF)

View file

@ -42,7 +42,7 @@ CMake configures the project according to variables you specify the command line
**NRF5_SDK_PATH**|path to the NRF52 SDK|`-DNRF5_SDK_PATH=/home/jf/nrf52/Pinetime/sdk`| **NRF5_SDK_PATH**|path to the NRF52 SDK|`-DNRF5_SDK_PATH=/home/jf/nrf52/Pinetime/sdk`|
**CMAKE_BUILD_TYPE (\*)**| Build type (Release or Debug). Release is applied by default if this variable is not specified.|`-DCMAKE_BUILD_TYPE=Debug` **CMAKE_BUILD_TYPE (\*)**| Build type (Release or Debug). Release is applied by default if this variable is not specified.|`-DCMAKE_BUILD_TYPE=Debug`
**BUILD_DFU (\*\*)**|Build DFU files while building (needs [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil)).|`-DBUILD_DFU=1` **BUILD_DFU (\*\*)**|Build DFU files while building (needs [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil)).|`-DBUILD_DFU=1`
**BUILD_RESOURCES (\*\*)**| Generate external resource while building (needs [lv_font_conv](https://github.com/lvgl/lv_font_conv) and [lv_img_conv](https://github.com/lvgl/lv_img_conv). |`-DBUILD_RESOURCES=1` **BUILD_RESOURCES (\*\*)**| Generate external resource while building (needs [lv_font_conv](https://github.com/lvgl/lv_font_conv) and [python3-pil/pillow](https://pillow.readthedocs.io) module). |`-DBUILD_RESOURCES=1`
**TARGET_DEVICE**|Target device, used for hardware configuration. Allowed: `PINETIME, MOY-TFK5, MOY-TIN5, MOY-TON5, MOY-UNK`|`-DTARGET_DEVICE=PINETIME` (Default) **TARGET_DEVICE**|Target device, used for hardware configuration. Allowed: `PINETIME, MOY-TFK5, MOY-TIN5, MOY-TON5, MOY-UNK`|`-DTARGET_DEVICE=PINETIME` (Default)
#### (\*) Note about **CMAKE_BUILD_TYPE** #### (\*) Note about **CMAKE_BUILD_TYPE**
@ -98,4 +98,4 @@ Binary files are generated into the folder `src`:
- **pinetime-mcuboot-app-image** : MCUBoot image of the firmware - **pinetime-mcuboot-app-image** : MCUBoot image of the firmware
- **pinetime-mcuboot-app-dfu** : DFU file of the firmware - **pinetime-mcuboot-app-dfu** : DFU file of the firmware
The same files are generated for **pinetime-recovery** and **pinetime-recoveryloader** The same files are generated for **pinetime-recovery** and **pinetime-recovery-loader**

View file

@ -9,59 +9,114 @@ This page will teach you:
The user interface of InfiniTime is made up of **screens**. The user interface of InfiniTime is made up of **screens**.
Screens that are opened from the app launcher are considered **apps**. Screens that are opened from the app launcher are considered **apps**.
Every app in InfiniTime is it's own class. Every app in InfiniTime is its own class.
An instance of the class is created when the app is launched, and destroyed when the user exits the app. An instance of the class is created when the app is launched, and destroyed when the user exits the app.
Apps run inside the "displayapp" task (briefly discussed [here](./Intro.md)). Apps run inside the `DisplayApp` task (briefly discussed [here](./Intro.md)).
Apps are responsible for everything drawn on the screen when they are running. Apps are responsible for everything drawn on the screen when they are running.
By default, apps only do something (as in a function is executed) when they are created or when a touch event is detected. Apps can be refreshed periodically and reacts to external events (touch or button).
## Interface ## Interface
Every app class has to be inside the namespace `Pinetime::Applications::Screens` and inherit from `Screen`. Every app class is declared inside the namespace `Pinetime::Applications::Screens`
The constructor should have at least one parameter `DisplayApp* app`, which it needs for the constructor of its parent class Screen. and inherits
Other parameters should be references to controllers that the app needs. from [`Pinetime::Applications::Screens::Screen`](https://github.com/InfiniTimeOrg/InfiniTime/blob/main/src/displayapp/screens/Screen.h).
A destructor is needed to clean up LVGL and restore any changes (for example re-enable sleeping).
App classes can override `bool OnButtonPushed()`, `bool OnTouchEvent(TouchEvents event)` and `bool OnTouchEvent(uint16_t x, uint16_t y)` to implement their own functionality for those events.
If an app only needs to display some text and do something upon a touch screen button press,
it does not need to override any of these functions, as LVGL can also handle touch events for you.
If you have any doubts, you can always look at how the other apps function for reference.
### Continuous updating Each app defines its own constructor.
The constructors mostly take references to InfiniTime `Controllers` (ex: Alarm, DateTime, BLE services, Settings,...)
the app needs for its operations. The constructor is responsible for initializing the UI of the app.
If your app needs to be updated continuously, you can do so by overriding the `Refresh()` function in your class The **destructor** cleans up LVGL and restores any changes (for example re-enable sleeping).
and calling `lv_task_create` inside the constructor.
An example call could look like this: App classes can override `bool OnButtonPushed()`, `bool OnTouchEvent(TouchEvents event)`
and `bool OnTouchEvent(uint16_t x, uint16_t y)` to implement their own functionality for those events.
```cpp Apps that need to be refreshed periodically create an `lv_task` (using `lv_task_create()`)
taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); that will call the method `Refresh()` periodically.
## App types
There are basically 2 types of applications : **system** apps and **user** apps.
**System** applications are always built into InfiniTime, and InfiniTime cannot work properly without those apps.
The watchfaces, settings, notifications and the application launcher are examples of such system applications.
**User** applications are optionally built into the firmware. They extend the functionalities of the system.
The distinction between **system** and **user** applications allows for more flexibility and customization.
This allows to easily select which user applications must be built into the firmware
without overflowing the system memory.
## Apps initialization
Apps are created by `DisplayApp` in `DisplayApp::LoadScreen()`.
This method simply call the creates an instance of the class that corresponds to the app specified in parameters.
The constructor of **system** apps is called directly. If the application is a **user** app,
the corresponding `AppDescription` is first retrieved from `userApps`
and then the function `create` is called to create an instance of the app.
## User application selection at build time
The list of user applications is generated at build time by the `consteval` function `CreateAppDescriptions()`
in `UserApps.h`. This method takes the list of applications that must be built into the firmware image.
This list of applications is defined as a list `Apps` enum values named `UserAppTypes` in `Apps.h`.
For each application listed in `UserAppTypes`, an entry of type `AppDescription` is added to the array `userApps`.
This entry is created by using the information provided by a template `AppTraits`
that is customized for every user application.
Here is an example of an AppTraits customized for the Alarm application.
It defines the type of application, its icon and a function that returns an instance of the application.
```c++
template <>
struct AppTraits<Apps::Alarm> {
static constexpr Apps app = Apps::Alarm;
static constexpr const char* icon = Screens::Symbols::clock;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Alarm(controllers.alarmController,
controllers.settingsController.GetClockType(),
*controllers.systemTask,
controllers.motorController);
};
};
``` ```
With `taskRefresh` being a member variable of your class and of type `lv_task_t*`. This array `userApps` is used by `DisplayApp` to create the applications and the `AppLauncher`
Remember to delete the task again using `lv_task_del`. to list all available applications.
The function `RefreshTaskCallback` is inherited from `Screen` and just calls your `Refresh` function.
## Creating your own app ## Creating your own app
A minimal app could look like this: A minimal user app could look like this:
MyApp.h: MyApp.h:
```cpp ```cpp
#pragma once #pragma once
#include "displayapp/Apps.h"
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include <lvgl/lvgl.h> #include "displayapp/Controllers.h"
#include "Symbols.h"
namespace Pinetime { namespace Pinetime {
namespace Applications { namespace Applications {
namespace Screens { namespace Screens {
class MyApp : public Screen { class MyApp : public Screen {
public: public:
MyApp(DisplayApp* app); MyApp();
~MyApp() override; ~MyApp() override;
}; };
} }
template <>
struct AppTraits<Apps:MyApp> {
static constexpr Apps app = Apps::MyApp;
static constexpr const char* icon = Screens::Symbol::myApp;
static Screens::Screens* Create(AppController& controllers) {
return new Screens::MyApp();
}
};
} }
} }
``` ```
@ -70,11 +125,10 @@ MyApp.cpp:
```cpp ```cpp
#include "displayapp/screens/MyApp.h" #include "displayapp/screens/MyApp.h"
#include "displayapp/DisplayApp.h"
using namespace Pinetime::Applications::Screens; using namespace Pinetime::Applications::Screens;
MyApp::MyApp(DisplayApp* app) : Screen(app) { MyApp::MyApp() {
lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr); lv_obj_t* title = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_text_static(title, "My test application"); lv_label_set_text_static(title, "My test application");
lv_label_set_align(title, LV_LABEL_ALIGN_CENTER); lv_label_set_align(title, LV_LABEL_ALIGN_CENTER);
@ -86,20 +140,24 @@ MyApp::~MyApp() {
} }
``` ```
Both of these files should be in [displayapp/screens/](/src/displayapp/screens/) Both of these files should be in [displayapp/screens/](/src/displayapp/screens/).
or [displayapp/screens/settings/](/src/displayapp/screens/settings/) if it's a setting app.
Now we have our very own app, but InfiniTime does not know about it yet. Now we have our very own app, but InfiniTime does not know about it yet.
The first step is to include your MyApp.cpp (or any new cpp files for that matter) The first step is to include your `MyApp.cpp` (or any new cpp files for that matter)
in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt). in the compilation by adding it to [CMakeLists.txt](/CMakeLists.txt).
The next step to making it launchable is to give your app an id. The next step to making it launch-able is to give your app an id.
To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/Apps.h](/src/displayapp/Apps.h)). To do this, add an entry in the enum class `Pinetime::Applications::Apps` ([displayapp/Apps.h](/src/displayapp/Apps.h)).
Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"` to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp). Name this entry after your app. Add `#include "displayapp/screens/MyApp.h"`
Now, go to the function `DisplayApp::LoadScreen` and add another case to the switch statement. to the file [displayapp/DisplayApp.cpp](/src/displayapp/DisplayApp.cpp).
If your application is a **system** application, go to the function `DisplayApp::LoadScreen`
and add another case to the switch statement.
The case will be the id you gave your app earlier. The case will be the id you gave your app earlier.
If your app needs any additional arguments, this is the place to pass them. If your app needs any additional arguments, this is the place to pass them.
If you want to add your app in the app launcher, add your app in [displayapp/screens/ApplicationList.h](/src/displayapp/screens/ApplicationList.h) to the array containing the applications and their corresponding symbol. If your app is a setting, do the same procedure in [displayapp/screens/settings/Settings.h](/src/displayapp/screens/settings/Settings.h). If your application is a **user** application, you don't need to add anything in DisplayApp,
everything will be automatically generated for you.
The user application will also be automatically be added to the app launcher menu.
You should now be able to [build](../buildAndProgram.md) the firmware You should now be able to [build](../buildAndProgram.md) the firmware
and flash it to your PineTime. Yay! and flash it to your PineTime. Yay!

View file

@ -1,7 +1,13 @@
FROM ubuntu:22.04 FROM ubuntu:22.04
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ARG NODE_MAJOR=20
RUN apt-get update -qq \ RUN apt-get update -qq \
&& apt-get install -y ca-certificates curl gnupg \
&& mkdir -p /etc/apt/keyrings \
&& curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \
&& apt-get update -qq \
&& apt-get install -y \ && apt-get install -y \
# x86_64 / generic packages # x86_64 / generic packages
bash \ bash \
@ -9,13 +15,14 @@ RUN apt-get update -qq \
cmake \ cmake \
git \ git \
make \ make \
nodejs \
python3 \ python3 \
python3-pip \ python3-pip \
python3-pil \
python-is-python3 \ python-is-python3 \
tar \ tar \
unzip \ unzip \
wget \ wget \
curl \
# aarch64 packages # aarch64 packages
libffi-dev \ libffi-dev \
libssl-dev \ libssl-dev \
@ -28,8 +35,6 @@ RUN apt-get update -qq \
libpango-1.0-0 \ libpango-1.0-0 \
ibpango1.0-dev \ ibpango1.0-dev \
libpangocairo-1.0-0 \ libpangocairo-1.0-0 \
&& curl -sL https://deb.nodesource.com/setup_18.x | bash - \
&& apt-get install -y nodejs \
&& rm -rf /var/cache/apt/* /var/lib/apt/lists/*; && rm -rf /var/cache/apt/* /var/lib/apt/lists/*;
# Git needed for PROJECT_GIT_COMMIT_HASH variable setting # Git needed for PROJECT_GIT_COMMIT_HASH variable setting
@ -39,10 +44,6 @@ RUN pip3 install -Iv cryptography==3.3
RUN pip3 install cbor RUN pip3 install cbor
RUN npm i lv_font_conv@1.5.2 -g RUN npm i lv_font_conv@1.5.2 -g
RUN npm i ts-node@10.9.1 -g
RUN npm i @swc/core -g
RUN npm i lv_img_conv@0.3.0 -g
# build.sh knows how to compile # build.sh knows how to compile
COPY build.sh /opt/ COPY build.sh /opt/

View file

@ -394,7 +394,6 @@ list(APPEND SOURCE_FILES
displayapp/screens/Notifications.cpp displayapp/screens/Notifications.cpp
displayapp/screens/Twos.cpp displayapp/screens/Twos.cpp
displayapp/screens/HeartRate.cpp displayapp/screens/HeartRate.cpp
displayapp/screens/Motion.cpp
displayapp/screens/FlashLight.cpp displayapp/screens/FlashLight.cpp
displayapp/screens/List.cpp displayapp/screens/List.cpp
displayapp/screens/CheckboxList.cpp displayapp/screens/CheckboxList.cpp

View file

@ -404,7 +404,8 @@ namespace Pinetime {
std::unique_ptr<WeatherData::Clouds>& WeatherService::GetCurrentClouds() { std::unique_ptr<WeatherData::Clouds>& WeatherService::GetCurrentClouds() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp(); uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) { for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Clouds && IsEventStillValid(header, currentTimestamp)) { if (header->eventType == WeatherData::eventtype::Clouds && currentTimestamp >= header->timestamp &&
IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::Clouds>&>(header); return reinterpret_cast<std::unique_ptr<WeatherData::Clouds>&>(header);
} }
} }
@ -415,7 +416,8 @@ namespace Pinetime {
std::unique_ptr<WeatherData::Obscuration>& WeatherService::GetCurrentObscuration() { std::unique_ptr<WeatherData::Obscuration>& WeatherService::GetCurrentObscuration() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp(); uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) { for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Obscuration && IsEventStillValid(header, currentTimestamp)) { if (header->eventType == WeatherData::eventtype::Obscuration && currentTimestamp >= header->timestamp &&
IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::Obscuration>&>(header); return reinterpret_cast<std::unique_ptr<WeatherData::Obscuration>&>(header);
} }
} }
@ -426,7 +428,8 @@ namespace Pinetime {
std::unique_ptr<WeatherData::Precipitation>& WeatherService::GetCurrentPrecipitation() { std::unique_ptr<WeatherData::Precipitation>& WeatherService::GetCurrentPrecipitation() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp(); uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) { for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Precipitation && IsEventStillValid(header, currentTimestamp)) { if (header->eventType == WeatherData::eventtype::Precipitation && currentTimestamp >= header->timestamp &&
IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::Precipitation>&>(header); return reinterpret_cast<std::unique_ptr<WeatherData::Precipitation>&>(header);
} }
} }
@ -437,7 +440,8 @@ namespace Pinetime {
std::unique_ptr<WeatherData::Wind>& WeatherService::GetCurrentWind() { std::unique_ptr<WeatherData::Wind>& WeatherService::GetCurrentWind() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp(); uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) { for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Wind && IsEventStillValid(header, currentTimestamp)) { if (header->eventType == WeatherData::eventtype::Wind && currentTimestamp >= header->timestamp &&
IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::Wind>&>(header); return reinterpret_cast<std::unique_ptr<WeatherData::Wind>&>(header);
} }
} }
@ -448,7 +452,8 @@ namespace Pinetime {
std::unique_ptr<WeatherData::Temperature>& WeatherService::GetCurrentTemperature() { std::unique_ptr<WeatherData::Temperature>& WeatherService::GetCurrentTemperature() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp(); uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) { for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Temperature && IsEventStillValid(header, currentTimestamp)) { if (header->eventType == WeatherData::eventtype::Temperature && currentTimestamp >= header->timestamp &&
IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::Temperature>&>(header); return reinterpret_cast<std::unique_ptr<WeatherData::Temperature>&>(header);
} }
} }
@ -459,7 +464,8 @@ namespace Pinetime {
std::unique_ptr<WeatherData::Humidity>& WeatherService::GetCurrentHumidity() { std::unique_ptr<WeatherData::Humidity>& WeatherService::GetCurrentHumidity() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp(); uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) { for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Humidity && IsEventStillValid(header, currentTimestamp)) { if (header->eventType == WeatherData::eventtype::Humidity && currentTimestamp >= header->timestamp &&
IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::Humidity>&>(header); return reinterpret_cast<std::unique_ptr<WeatherData::Humidity>&>(header);
} }
} }
@ -470,7 +476,8 @@ namespace Pinetime {
std::unique_ptr<WeatherData::Pressure>& WeatherService::GetCurrentPressure() { std::unique_ptr<WeatherData::Pressure>& WeatherService::GetCurrentPressure() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp(); uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) { for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Pressure && IsEventStillValid(header, currentTimestamp)) { if (header->eventType == WeatherData::eventtype::Pressure && currentTimestamp >= header->timestamp &&
IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::Pressure>&>(header); return reinterpret_cast<std::unique_ptr<WeatherData::Pressure>&>(header);
} }
} }
@ -481,7 +488,8 @@ namespace Pinetime {
std::unique_ptr<WeatherData::Location>& WeatherService::GetCurrentLocation() { std::unique_ptr<WeatherData::Location>& WeatherService::GetCurrentLocation() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp(); uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) { for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::Location && IsEventStillValid(header, currentTimestamp)) { if (header->eventType == WeatherData::eventtype::Location && currentTimestamp >= header->timestamp &&
IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::Location>&>(header); return reinterpret_cast<std::unique_ptr<WeatherData::Location>&>(header);
} }
} }
@ -492,7 +500,8 @@ namespace Pinetime {
std::unique_ptr<WeatherData::AirQuality>& WeatherService::GetCurrentQuality() { std::unique_ptr<WeatherData::AirQuality>& WeatherService::GetCurrentQuality() {
uint64_t currentTimestamp = GetCurrentUnixTimestamp(); uint64_t currentTimestamp = GetCurrentUnixTimestamp();
for (auto&& header : this->timeline) { for (auto&& header : this->timeline) {
if (header->eventType == WeatherData::eventtype::AirQuality && IsEventStillValid(header, currentTimestamp)) { if (header->eventType == WeatherData::eventtype::AirQuality && currentTimestamp >= header->timestamp &&
IsEventStillValid(header, currentTimestamp)) {
return reinterpret_cast<std::unique_ptr<WeatherData::AirQuality>&>(header); return reinterpret_cast<std::unique_ptr<WeatherData::AirQuality>&>(header);
} }
} }

View file

@ -141,7 +141,7 @@ void Gfx::SetBackgroundColor(uint16_t color) {
bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) { bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) {
if (!state.busy) if (!state.busy)
return false; return false;
state.remainingIterations--; state.remainingIterations = state.remainingIterations - 1;
if (state.remainingIterations == 0) { if (state.remainingIterations == 0) {
state.busy = false; state.busy = false;
NotifyEndOfTransfer(state.taskToNotify); NotifyEndOfTransfer(state.taskToNotify);
@ -170,7 +170,7 @@ bool Gfx::GetNextBuffer(uint8_t** data, size_t& size) {
size = bytes_in_line * 8 * 2; size = bytes_in_line * 8 * 2;
} }
state.currentIteration++; state.currentIteration = state.currentIteration + 1;
return true; return true;
} }

View file

@ -1,5 +1,5 @@
#pragma once #pragma once
#include <cstddef>
namespace Pinetime { namespace Pinetime {
namespace Applications { namespace Applications {
enum class Apps { enum class Apps {
@ -37,7 +37,33 @@ namespace Pinetime {
SettingChimes, SettingChimes,
SettingShakeThreshold, SettingShakeThreshold,
SettingBluetooth, SettingBluetooth,
Error Error,
Weather
}; };
template <Apps>
struct AppTraits {};
template <Apps... As>
struct TypeList {
static constexpr size_t Count = sizeof...(As);
};
using UserAppTypes = TypeList<Apps::Alarm,
Apps::HeartRate,
Apps::Paint,
Apps::Metronome,
Apps::Music,
Apps::Navigation,
Apps::Paddle,
Apps::Steps,
Apps::StopWatch,
Apps::Timer,
Apps::Twos
/*
Apps::Weather,
Apps::Motion
*/
>;
} }
} }

View file

@ -0,0 +1,56 @@
#pragma once
namespace Pinetime {
namespace Applications {
class DisplayApp;
}
namespace Components {
class LittleVgl;
}
namespace Controllers {
class Battery;
class Ble;
class DateTime;
class NotificationManager;
class HeartRateController;
class Settings;
class MotorController;
class MotionController;
class AlarmController;
class BrightnessController;
class WeatherService;
class FS;
class Timer;
class MusicService;
class NavigationService;
}
namespace System {
class SystemTask;
}
namespace Applications {
struct AppControllers {
const Pinetime::Controllers::Battery& batteryController;
const Pinetime::Controllers::Ble& bleController;
Pinetime::Controllers::DateTime& dateTimeController;
Pinetime::Controllers::NotificationManager& notificationManager;
Pinetime::Controllers::HeartRateController& heartRateController;
Pinetime::Controllers::Settings& settingsController;
Pinetime::Controllers::MotorController& motorController;
Pinetime::Controllers::MotionController& motionController;
Pinetime::Controllers::AlarmController& alarmController;
Pinetime::Controllers::BrightnessController& brightnessController;
Pinetime::Controllers::WeatherService* weatherController;
Pinetime::Controllers::FS& filesystem;
Pinetime::Controllers::Timer& timer;
Pinetime::System::SystemTask* systemTask;
Pinetime::Applications::DisplayApp* displayApp;
Pinetime::Components::LittleVgl& lvgl;
Pinetime::Controllers::MusicService* musicService;
Pinetime::Controllers::NavigationService* navigationService;
};
}
}

View file

@ -50,6 +50,7 @@
#include "displayapp/screens/settings/SettingBluetooth.h" #include "displayapp/screens/settings/SettingBluetooth.h"
#include "libs/lv_conf.h" #include "libs/lv_conf.h"
#include "UserApps.h"
using namespace Pinetime::Applications; using namespace Pinetime::Applications;
using namespace Pinetime::Applications::Display; using namespace Pinetime::Applications::Display;
@ -97,7 +98,25 @@ DisplayApp::DisplayApp(Drivers::St7789& lcd,
filesystem {filesystem}, filesystem {filesystem},
lvgl {lcd, filesystem}, lvgl {lcd, filesystem},
timer(this, TimerCallback), timer(this, TimerCallback),
popupMessage {"Touch input\nis ignored,\npush button\nto unlock."} { popupMessage {"Touch input\nis ignored,\npush button\nto unlock."},
controllers {batteryController,
bleController,
dateTimeController,
notificationManager,
heartRateController,
settingsController,
motorController,
motionController,
alarmController,
brightnessController,
nullptr,
filesystem,
timer,
nullptr,
this,
lvgl,
nullptr,
nullptr} {
} }
void DisplayApp::Start(System::BootErrors error) { void DisplayApp::Start(System::BootErrors error) {
@ -409,14 +428,20 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
SetFullRefresh(direction); SetFullRefresh(direction);
switch (app) { switch (app) {
case Apps::Launcher: case Apps::Launcher: {
currentScreen = std::array<Screens::Tile::Applications, UserAppTypes::Count> apps;
std::make_unique<Screens::ApplicationList>(this, settingsController, batteryController, bleController, dateTimeController, filesystem); int i = 0;
break; for (const auto& userApp : userApps) {
case Apps::Motion: apps[i++] = Screens::Tile::Applications {userApp.icon, userApp.app, true};
// currentScreen = std::make_unique<Screens::Motion>(motionController); }
// break; currentScreen = std::make_unique<Screens::ApplicationList>(this,
case Apps::None: settingsController,
batteryController,
bleController,
dateTimeController,
filesystem,
std::move(apps));
} break;
case Apps::Clock: case Apps::Clock:
currentScreen = std::make_unique<Screens::Clock>(dateTimeController, currentScreen = std::make_unique<Screens::Clock>(dateTimeController,
batteryController, batteryController,
@ -428,7 +453,6 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
systemTask->nimble().weather(), systemTask->nimble().weather(),
filesystem); filesystem);
break; break;
case Apps::Error: case Apps::Error:
currentScreen = std::make_unique<Screens::Error>(bootError); currentScreen = std::make_unique<Screens::Error>(bootError);
break; break;
@ -460,14 +484,6 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
*systemTask, *systemTask,
Screens::Notifications::Modes::Preview); Screens::Notifications::Modes::Preview);
break; break;
case Apps::Timer:
currentScreen = std::make_unique<Screens::Timer>(timer);
break;
case Apps::Alarm:
currentScreen = std::make_unique<Screens::Alarm>(alarmController, settingsController.GetClockType(), *systemTask, motorController);
break;
// Settings
case Apps::QuickSettings: case Apps::QuickSettings:
currentScreen = std::make_unique<Screens::QuickSettings>(this, currentScreen = std::make_unique<Screens::QuickSettings>(this,
batteryController, batteryController,
@ -523,38 +539,25 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio
case Apps::FlashLight: case Apps::FlashLight:
currentScreen = std::make_unique<Screens::FlashLight>(*systemTask, brightnessController); currentScreen = std::make_unique<Screens::FlashLight>(*systemTask, brightnessController);
break; break;
case Apps::StopWatch: default: {
currentScreen = std::make_unique<Screens::StopWatch>(*systemTask); const auto* d = std::find_if(userApps.begin(), userApps.end(), [app](const AppDescription& appDescription) {
break; return appDescription.app == app;
case Apps::Twos: });
currentScreen = std::make_unique<Screens::Twos>(); if (d != userApps.end())
break; currentScreen.reset(d->create(controllers));
case Apps::Paint: else {
currentScreen = std::make_unique<Screens::InfiniPaint>(lvgl, motorController); currentScreen = std::make_unique<Screens::Clock>(dateTimeController,
break; batteryController,
case Apps::Paddle: bleController,
currentScreen = std::make_unique<Screens::Paddle>(lvgl); notificationManager,
break; settingsController,
case Apps::Music: heartRateController,
currentScreen = std::make_unique<Screens::Music>(systemTask->nimble().music()); motionController,
break; systemTask->nimble().weather(),
case Apps::Navigation: filesystem);
currentScreen = std::make_unique<Screens::Navigation>(systemTask->nimble().navigation()); }
break;
case Apps::HeartRate:
currentScreen = std::make_unique<Screens::HeartRate>(heartRateController, *systemTask);
break;
case Apps::Metronome:
currentScreen = std::make_unique<Screens::Metronome>(motorController, *systemTask);
break;
/* Weather debug app
case Apps::Weather:
currentScreen = std::make_unique<Screens::Weather>(this, systemTask->nimble().weather());
break;
*/
case Apps::Steps:
currentScreen = std::make_unique<Screens::Steps>(motionController, settingsController);
break; break;
}
} }
currentApp = app; currentApp = app;
} }
@ -567,7 +570,15 @@ void DisplayApp::PushMessage(Messages msg) {
portYIELD_FROM_ISR(xHigherPriorityTaskWoken); portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
} }
} else { } else {
xQueueSend(msgQueue, &msg, portMAX_DELAY); TickType_t timeout = portMAX_DELAY;
// Make xQueueSend() non-blocking if the message is a Notification message. We do this to avoid
// deadlock between SystemTask and DisplayApp when their respective message queues are getting full
// when a lot of notifications are received on a very short time span.
if (msg == Messages::NewNotification) {
timeout = static_cast<TickType_t>(0);
}
xQueueSend(msgQueue, &msg, timeout);
} }
} }
@ -604,6 +615,19 @@ void DisplayApp::PushMessageToSystemTask(Pinetime::System::Messages message) {
void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) { void DisplayApp::Register(Pinetime::System::SystemTask* systemTask) {
this->systemTask = systemTask; this->systemTask = systemTask;
this->controllers.systemTask = systemTask;
}
void DisplayApp::Register(Pinetime::Controllers::WeatherService* weatherService) {
this->controllers.weatherController = weatherService;
}
void DisplayApp::Register(Pinetime::Controllers::MusicService* musicService) {
this->controllers.musicService = musicService;
}
void DisplayApp::Register(Pinetime::Controllers::NavigationService* NavigationService) {
this->controllers.navigationService = NavigationService;
} }
void DisplayApp::ApplyBrightness() { void DisplayApp::ApplyBrightness() {

View file

@ -21,6 +21,7 @@
#include "BootErrors.h" #include "BootErrors.h"
#include "utility/StaticStack.h" #include "utility/StaticStack.h"
#include "displayapp/Controllers.h"
namespace Pinetime { namespace Pinetime {
@ -74,6 +75,9 @@ namespace Pinetime {
void SetFullRefresh(FullRefreshDirections direction); void SetFullRefresh(FullRefreshDirections direction);
void Register(Pinetime::System::SystemTask* systemTask); void Register(Pinetime::System::SystemTask* systemTask);
void Register(Pinetime::Controllers::WeatherService* weatherService);
void Register(Pinetime::Controllers::MusicService* musicService);
void Register(Pinetime::Controllers::NavigationService* NavigationService);
private: private:
Pinetime::Drivers::St7789& lcd; Pinetime::Drivers::St7789& lcd;
@ -99,6 +103,7 @@ namespace Pinetime {
Pinetime::Applications::Widgets::PopupMessage popupMessage; Pinetime::Applications::Widgets::PopupMessage popupMessage;
AppControllers controllers;
TaskHandle_t taskHandle; TaskHandle_t taskHandle;
States state = States::Running; States state = States::Running;

View file

@ -121,3 +121,12 @@ void DisplayApp::PushMessage(Display::Messages msg) {
void DisplayApp::Register(Pinetime::System::SystemTask* /*systemTask*/) { void DisplayApp::Register(Pinetime::System::SystemTask* /*systemTask*/) {
} }
void DisplayApp::Register(Pinetime::Controllers::WeatherService* /*weatherService*/) {
}
void DisplayApp::Register(Pinetime::Controllers::MusicService* /*musicService*/) {
}
void DisplayApp::Register(Pinetime::Controllers::NavigationService* /*NavigationService*/) {
}

View file

@ -34,6 +34,9 @@ namespace Pinetime {
class AlarmController; class AlarmController;
class BrightnessController; class BrightnessController;
class FS; class FS;
class WeatherService;
class MusicService;
class NavigationService;
} }
namespace System { namespace System {
@ -66,6 +69,9 @@ namespace Pinetime {
void PushMessage(Pinetime::Applications::Display::Messages msg); void PushMessage(Pinetime::Applications::Display::Messages msg);
void Register(Pinetime::System::SystemTask* systemTask); void Register(Pinetime::System::SystemTask* systemTask);
void Register(Pinetime::Controllers::WeatherService* weatherService);
void Register(Pinetime::Controllers::MusicService* musicService);
void Register(Pinetime::Controllers::NavigationService* NavigationService);
private: private:
TaskHandle_t taskHandle; TaskHandle_t taskHandle;

36
src/displayapp/UserApps.h Normal file
View file

@ -0,0 +1,36 @@
#pragma once
#include "Apps.h"
#include "Controllers.h"
#include "displayapp/screens/Alarm.h"
#include "displayapp/screens/Timer.h"
#include "displayapp/screens/Twos.h"
#include "displayapp/screens/Tile.h"
#include "displayapp/screens/ApplicationList.h"
#include "displayapp/screens/Clock.h"
namespace Pinetime {
namespace Applications {
namespace Screens {
class Screen;
}
struct AppDescription {
Apps app;
const char* icon;
Screens::Screen* (*create)(AppControllers& controllers);
};
template <Apps t>
consteval AppDescription CreateAppDescription() {
return {AppTraits<t>::app, AppTraits<t>::icon, &AppTraits<t>::Create};
}
template <template <Apps...> typename T, Apps... ts>
consteval std::array<AppDescription, sizeof...(ts)> CreateAppDescriptions(T<ts...>) {
return {CreateAppDescription<ts>()...};
}
constexpr auto userApps = CreateAppDescriptions(UserAppTypes {});
}
}

View file

@ -18,7 +18,7 @@
"sources": [ "sources": [
{ {
"file": "JetBrainsMono-Regular.ttf", "file": "JetBrainsMono-Regular.ttf",
"range": "0x25, 0x2b, 0x2d, 0x30-0x3a" "range": "0x25, 0x2b, 0x2d, 0x30-0x3a, 0x4b-0x4d, 0x66, 0x69, 0x6b, 0x6d, 0x74"
} }
], ],
"bpp": 1, "bpp": 1,

View file

@ -19,6 +19,10 @@
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "displayapp/screens/Symbols.h" #include "displayapp/screens/Symbols.h"
#include "displayapp/InfiniTimeTheme.h" #include "displayapp/InfiniTimeTheme.h"
#include "components/settings/Settings.h"
#include "components/alarm/AlarmController.h"
#include "components/motor/MotorController.h"
#include "systemtask/SystemTask.h"
using namespace Pinetime::Applications::Screens; using namespace Pinetime::Applications::Screens;
using Pinetime::Controllers::AlarmController; using Pinetime::Controllers::AlarmController;

View file

@ -17,21 +17,22 @@
*/ */
#pragma once #pragma once
#include "displayapp/Apps.h"
#include "components/settings/Settings.h"
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "systemtask/SystemTask.h"
#include "displayapp/LittleVgl.h"
#include "components/alarm/AlarmController.h"
#include "displayapp/widgets/Counter.h" #include "displayapp/widgets/Counter.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"
namespace Pinetime { namespace Pinetime {
namespace Applications { namespace Applications {
namespace Screens { namespace Screens {
class Alarm : public Screen { class Alarm : public Screen {
public: public:
Alarm(Controllers::AlarmController& alarmController, explicit Alarm(Controllers::AlarmController& alarmController,
Controllers::Settings::ClockType clockType, Controllers::Settings::ClockType clockType,
System::SystemTask& systemTask, System::SystemTask& systemTask,
Controllers::MotorController& motorController); Controllers::MotorController& motorController);
~Alarm() override; ~Alarm() override;
void SetAlerting(); void SetAlerting();
void OnButtonEvent(lv_obj_t* obj, lv_event_t event); void OnButtonEvent(lv_obj_t* obj, lv_event_t event);
@ -63,6 +64,19 @@ namespace Pinetime {
Widgets::Counter hourCounter = Widgets::Counter(0, 23, jetbrains_mono_76); Widgets::Counter hourCounter = Widgets::Counter(0, 23, jetbrains_mono_76);
Widgets::Counter minuteCounter = Widgets::Counter(0, 59, jetbrains_mono_76); Widgets::Counter minuteCounter = Widgets::Counter(0, 59, jetbrains_mono_76);
}; };
}
template <>
struct AppTraits<Apps::Alarm> {
static constexpr Apps app = Apps::Alarm;
static constexpr const char* icon = Screens::Symbols::clock;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Alarm(controllers.alarmController,
controllers.settingsController.GetClockType(),
*controllers.systemTask,
controllers.motorController);
};
}; };
}; }
} }

View file

@ -1,8 +1,9 @@
#include "displayapp/screens/ApplicationList.h" #include "displayapp/screens/ApplicationList.h"
#include "displayapp/screens/Tile.h"
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include <functional> #include <functional>
#include "displayapp/Apps.h" #include <algorithm>
#include "displayapp/DisplayApp.h" #include "components/settings/Settings.h"
using namespace Pinetime::Applications::Screens; using namespace Pinetime::Applications::Screens;
@ -16,18 +17,20 @@ auto ApplicationList::CreateScreenList() const {
return screens; return screens;
} }
ApplicationList::ApplicationList(Pinetime::Applications::DisplayApp* app, ApplicationList::ApplicationList(DisplayApp* app,
Pinetime::Controllers::Settings& settingsController, Pinetime::Controllers::Settings& settingsController,
const Pinetime::Controllers::Battery& batteryController, const Pinetime::Controllers::Battery& batteryController,
const Pinetime::Controllers::Ble& bleController, const Pinetime::Controllers::Ble& bleController,
Controllers::DateTime& dateTimeController, Controllers::DateTime& dateTimeController,
Pinetime::Controllers::FS& filesystem) Pinetime::Controllers::FS& filesystem,
std::array<Tile::Applications, UserAppTypes::Count>&& apps)
: app {app}, : app {app},
settingsController {settingsController}, settingsController {settingsController},
batteryController {batteryController}, batteryController {batteryController},
bleController {bleController}, bleController {bleController},
dateTimeController {dateTimeController}, dateTimeController {dateTimeController},
filesystem{filesystem}, filesystem {filesystem},
apps {std::move(apps)},
screens {app, settingsController.GetAppMenu(), CreateScreenList(), Screens::ScreenListModes::UpDown} { screens {app, settingsController.GetAppMenu(), CreateScreenList(), Screens::ScreenListModes::UpDown} {
} }
@ -40,9 +43,14 @@ bool ApplicationList::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
} }
std::unique_ptr<Screen> ApplicationList::CreateScreen(unsigned int screenNum) const { std::unique_ptr<Screen> ApplicationList::CreateScreen(unsigned int screenNum) const {
std::array<Tile::Applications, appsPerScreen> apps; std::array<Tile::Applications, appsPerScreen> pageApps;
for (int i = 0; i < appsPerScreen; i++) { for (int i = 0; i < appsPerScreen; i++) {
apps[i] = applications[screenNum * appsPerScreen + i]; if (i + (screenNum * appsPerScreen) >= apps.size()) {
pageApps[i] = {"", Pinetime::Applications::Apps::None, false};
} else {
pageApps[i] = apps[i + (screenNum * appsPerScreen)];
}
} }
return std::make_unique<Screens::Tile>(screenNum, return std::make_unique<Screens::Tile>(screenNum,
@ -52,5 +60,5 @@ std::unique_ptr<Screen> ApplicationList::CreateScreen(unsigned int screenNum) co
batteryController, batteryController,
bleController, bleController,
dateTimeController, dateTimeController,
apps); pageApps);
} }

View file

@ -2,15 +2,12 @@
#include <array> #include <array>
#include <memory> #include <memory>
#include "displayapp/Apps.h"
#include "displayapp/screens/Screen.h" #include "Screen.h"
#include "displayapp/screens/ScreenList.h" #include "ScreenList.h"
#include "components/datetime/DateTimeController.h" #include "displayapp/Controllers.h"
#include "components/settings/Settings.h" #include "Symbols.h"
#include "components/battery/BatteryController.h" #include "Tile.h"
#include "displayapp/screens/Symbols.h"
#include "displayapp/screens/Tile.h"
#include "displayapp/screens/Navigation.h"
namespace Pinetime { namespace Pinetime {
namespace Applications { namespace Applications {
@ -22,7 +19,8 @@ namespace Pinetime {
const Pinetime::Controllers::Battery& batteryController, const Pinetime::Controllers::Battery& batteryController,
const Pinetime::Controllers::Ble& bleController, const Pinetime::Controllers::Ble& bleController,
Controllers::DateTime& dateTimeController, Controllers::DateTime& dateTimeController,
Pinetime::Controllers::FS& filesystem); Pinetime::Controllers::FS& filesystem,
std::array<Tile::Applications, UserAppTypes::Count>&& apps);
~ApplicationList() override; ~ApplicationList() override;
bool OnTouchEvent(TouchEvents event) override; bool OnTouchEvent(TouchEvents event) override;
@ -36,29 +34,13 @@ namespace Pinetime {
const Pinetime::Controllers::Ble& bleController; const Pinetime::Controllers::Ble& bleController;
Controllers::DateTime& dateTimeController; Controllers::DateTime& dateTimeController;
Pinetime::Controllers::FS& filesystem; Pinetime::Controllers::FS& filesystem;
std::array<Tile::Applications, UserAppTypes::Count> apps;
static constexpr int appsPerScreen = 6; static constexpr int appsPerScreen = 6;
// Increment this when more space is needed // Increment this when more space is needed
static constexpr int nScreens = 2; static constexpr int nScreens = (UserAppTypes::Count / appsPerScreen) + 1;
std::array<Tile::Applications, appsPerScreen * nScreens> applications {{
{Symbols::stopWatch, Apps::StopWatch, true},
{Symbols::clock, Apps::Alarm, true},
{Symbols::hourGlass, Apps::Timer, true},
{Symbols::shoe, Apps::Steps, true},
{Symbols::heartBeat, Apps::HeartRate, true},
{Symbols::music, Apps::Music, true},
{Symbols::paintbrush, Apps::Paint, true},
{Symbols::paddle, Apps::Paddle, true},
{"2", Apps::Twos, true},
{Symbols::drum, Apps::Metronome, true},
{Symbols::map, Apps::Navigation, Applications::Screens::Navigation::IsAvailable(filesystem)},
{Symbols::none, Apps::None, false},
// {"M", Apps::Motion},
}};
ScreenList<nScreens> screens; ScreenList<nScreens> screens;
}; };
} }

View file

@ -1,13 +1,12 @@
#pragma once #pragma once
#include <lvgl/src/lv_core/lv_obj.h>
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <components/heartrate/HeartRateController.h> #include "displayapp/Controllers.h"
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "components/datetime/DateTimeController.h" #include "displayapp/Apps.h"
#include "components/ble/weather/WeatherService.h" #include "Symbols.h"
namespace Pinetime { namespace Pinetime {
namespace Controllers { namespace Controllers {
@ -16,6 +15,10 @@ namespace Pinetime {
class Ble; class Ble;
class NotificationManager; class NotificationManager;
class MotionController; class MotionController;
class DateTime;
class HeartRateController;
class WeatherService;
class FS;
} }
namespace Applications { namespace Applications {

View file

@ -4,6 +4,7 @@
#include <chrono> #include <chrono>
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "Symbols.h"
#include <lvgl/src/lv_core/lv_style.h> #include <lvgl/src/lv_core/lv_style.h>
#include <lvgl/src/lv_core/lv_obj.h> #include <lvgl/src/lv_core/lv_obj.h>
@ -37,5 +38,15 @@ namespace Pinetime {
lv_task_t* taskRefresh; lv_task_t* taskRefresh;
}; };
} }
template <>
struct AppTraits<Apps::HeartRate> {
static constexpr Apps app = Apps::HeartRate;
static constexpr const char* icon = Screens::Symbols::heartBeat;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::HeartRate(controllers.heartRateController, *controllers.systemTask);
};
};
} }
} }

View file

@ -5,6 +5,9 @@
#include <algorithm> // std::fill #include <algorithm> // std::fill
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "components/motor/MotorController.h" #include "components/motor/MotorController.h"
#include "Symbols.h"
#include <displayapp/Apps.h>
#include <displayapp/Controllers.h>
namespace Pinetime { namespace Pinetime {
namespace Components { namespace Components {
@ -35,5 +38,15 @@ namespace Pinetime {
uint8_t color = 2; uint8_t color = 2;
}; };
} }
template <>
struct AppTraits<Apps::Paint> {
static constexpr Apps app = Apps::Paint;
static constexpr const char* icon = Screens::Symbols::paintbrush;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::InfiniPaint(controllers.lvgl, controllers.motorController);
};
};
} }
} }

View file

@ -3,6 +3,7 @@
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "components/motor/MotorController.h" #include "components/motor/MotorController.h"
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "Symbols.h"
namespace Pinetime { namespace Pinetime {
namespace Applications { namespace Applications {
@ -36,5 +37,15 @@ namespace Pinetime {
lv_task_t* taskRefresh; lv_task_t* taskRefresh;
}; };
} }
template <>
struct AppTraits<Apps::Metronome> {
static constexpr Apps app = Apps::Metronome;
static constexpr const char* icon = Screens::Symbols::drum;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Metronome(controllers.motorController, *controllers.systemTask);
};
};
} }
} }

View file

@ -6,6 +6,8 @@
#include <lvgl/src/lv_core/lv_style.h> #include <lvgl/src/lv_core/lv_style.h>
#include <lvgl/src/lv_core/lv_obj.h> #include <lvgl/src/lv_core/lv_obj.h>
#include <components/motion/MotionController.h> #include <components/motion/MotionController.h>
#include "displayapp/Controllers.h"
#include "displayapp/Apps.h"
namespace Pinetime { namespace Pinetime {
namespace Applications { namespace Applications {
@ -30,5 +32,15 @@ namespace Pinetime {
lv_task_t* taskRefresh; lv_task_t* taskRefresh;
}; };
} }
template <>
struct AppTraits<Apps::Motion> {
static constexpr Apps app = Apps::Motion;
static constexpr const char* icon = "M";
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Motion(controllers.motionController);
};
};
} }
} }

View file

@ -21,6 +21,9 @@
#include <lvgl/src/lv_core/lv_obj.h> #include <lvgl/src/lv_core/lv_obj.h>
#include <string> #include <string>
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "displayapp/Apps.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"
namespace Pinetime { namespace Pinetime {
namespace Controllers { namespace Controllers {
@ -82,5 +85,15 @@ namespace Pinetime {
/** Watchapp */ /** Watchapp */
}; };
} }
template <>
struct AppTraits<Apps::Music> {
static constexpr Apps app = Apps::Music;
static constexpr const char* icon = Screens::Symbols::music;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Music(*controllers.musicService);
};
};
} }
} }

View file

@ -203,19 +203,21 @@ Navigation::Navigation(Pinetime::Controllers::NavigationService& nav) : navServi
lv_obj_align(imgFlag, nullptr, LV_ALIGN_CENTER, 0, -60); lv_obj_align(imgFlag, nullptr, LV_ALIGN_CENTER, 0, -60);
txtNarrative = lv_label_create(lv_scr_act(), nullptr); txtNarrative = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_long_mode(txtNarrative, LV_LABEL_LONG_BREAK); lv_label_set_long_mode(txtNarrative, LV_LABEL_LONG_DOT);
lv_obj_set_width(txtNarrative, LV_HOR_RES); lv_obj_set_width(txtNarrative, LV_HOR_RES);
lv_obj_set_height(txtNarrative, 80);
lv_label_set_text_static(txtNarrative, "Navigation"); lv_label_set_text_static(txtNarrative, "Navigation");
lv_label_set_align(txtNarrative, LV_LABEL_ALIGN_CENTER); lv_label_set_align(txtNarrative, LV_LABEL_ALIGN_CENTER);
lv_obj_align(txtNarrative, nullptr, LV_ALIGN_CENTER, 0, 10); lv_obj_align(txtNarrative, nullptr, LV_ALIGN_CENTER, 0, 30);
txtManDist = lv_label_create(lv_scr_act(), nullptr); txtManDist = lv_label_create(lv_scr_act(), nullptr);
lv_label_set_long_mode(txtManDist, LV_LABEL_LONG_BREAK); lv_label_set_long_mode(txtManDist, LV_LABEL_LONG_BREAK);
lv_obj_set_style_local_text_color(txtManDist, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN); lv_obj_set_style_local_text_color(txtManDist, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_GREEN);
lv_obj_set_style_local_text_font(txtManDist, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_42);
lv_obj_set_width(txtManDist, LV_HOR_RES); lv_obj_set_width(txtManDist, LV_HOR_RES);
lv_label_set_text_static(txtManDist, "--M"); lv_label_set_text_static(txtManDist, "--M");
lv_label_set_align(txtManDist, LV_LABEL_ALIGN_CENTER); lv_label_set_align(txtManDist, LV_LABEL_ALIGN_CENTER);
lv_obj_align(txtManDist, nullptr, LV_ALIGN_CENTER, 0, 60); lv_obj_align(txtManDist, nullptr, LV_ALIGN_CENTER, 0, 90);
// Route Progress // Route Progress
barProgress = lv_bar_create(lv_scr_act(), nullptr); barProgress = lv_bar_create(lv_scr_act(), nullptr);

View file

@ -22,6 +22,9 @@
#include <string> #include <string>
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include <array> #include <array>
#include "displayapp/Apps.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"
namespace Pinetime { namespace Pinetime {
namespace Controllers { namespace Controllers {
@ -55,5 +58,15 @@ namespace Pinetime {
lv_task_t* taskRefresh; lv_task_t* taskRefresh;
}; };
} }
template <>
struct AppTraits<Apps::Navigation> {
static constexpr Apps app = Apps::Navigation;
static constexpr const char* icon = Screens::Symbols::map;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Navigation(*controllers.navigationService);
};
};
} }
} }

View file

@ -3,6 +3,9 @@
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include <cstdint> #include <cstdint>
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "displayapp/Apps.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"
namespace Pinetime { namespace Pinetime {
namespace Components { namespace Components {
@ -45,5 +48,15 @@ namespace Pinetime {
lv_task_t* taskRefresh; lv_task_t* taskRefresh;
}; };
} }
template <>
struct AppTraits<Apps::Paddle> {
static constexpr Apps app = Apps::Paddle;
static constexpr const char* icon = Screens::Symbols::paddle;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Paddle(controllers.lvgl);
};
};
} }
} }

View file

@ -4,6 +4,9 @@
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include <components/motion/MotionController.h> #include <components/motion/MotionController.h>
#include "displayapp/Apps.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"
namespace Pinetime { namespace Pinetime {
@ -39,5 +42,15 @@ namespace Pinetime {
lv_task_t* taskRefresh; lv_task_t* taskRefresh;
}; };
} }
template <>
struct AppTraits<Apps::Steps> {
static constexpr Apps app = Apps::Steps;
static constexpr const char* icon = Screens::Symbols::shoe;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Steps(controllers.motionController, controllers.settingsController);
};
};
} }
} }

View file

@ -7,50 +7,67 @@
#include "portmacro_cmsis.h" #include "portmacro_cmsis.h"
#include "systemtask/SystemTask.h" #include "systemtask/SystemTask.h"
#include "displayapp/Apps.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"
namespace Pinetime::Applications::Screens { namespace Pinetime {
namespace Applications {
namespace Screens {
enum class States { Init, Running, Halted }; enum class States { Init, Running, Halted };
struct TimeSeparated_t { struct TimeSeparated_t {
int hours; int hours;
int mins; int mins;
int secs; int secs;
int hundredths; int hundredths;
}; };
class StopWatch : public Screen { class StopWatch : public Screen {
public: public:
explicit StopWatch(System::SystemTask& systemTask); explicit StopWatch(System::SystemTask& systemTask);
~StopWatch() override; ~StopWatch() override;
void Refresh() override; void Refresh() override;
void playPauseBtnEventHandler(); void playPauseBtnEventHandler();
void stopLapBtnEventHandler(); void stopLapBtnEventHandler();
bool OnButtonPushed() override; bool OnButtonPushed() override;
private: private:
void SetInterfacePaused(); void SetInterfacePaused();
void SetInterfaceRunning(); void SetInterfaceRunning();
void SetInterfaceStopped(); void SetInterfaceStopped();
void Reset(); void Reset();
void Start(); void Start();
void Pause(); void Pause();
Pinetime::System::SystemTask& systemTask; Pinetime::System::SystemTask& systemTask;
States currentState = States::Init; States currentState = States::Init;
TickType_t startTime; TickType_t startTime;
TickType_t oldTimeElapsed = 0; TickType_t oldTimeElapsed = 0;
TickType_t blinkTime = 0; TickType_t blinkTime = 0;
static constexpr int maxLapCount = 20; static constexpr int maxLapCount = 20;
TickType_t laps[maxLapCount + 1]; TickType_t laps[maxLapCount + 1];
static constexpr int displayedLaps = 2; static constexpr int displayedLaps = 2;
int lapsDone = 0; int lapsDone = 0;
lv_obj_t *time, *msecTime, *btnPlayPause, *btnStopLap, *txtPlayPause, *txtStopLap; lv_obj_t *time, *msecTime, *btnPlayPause, *btnStopLap, *txtPlayPause, *txtStopLap;
lv_obj_t* lapText; lv_obj_t* lapText;
bool isHoursLabelUpdated = false; bool isHoursLabelUpdated = false;
lv_task_t* taskRefresh; lv_task_t* taskRefresh;
}; };
}
template <>
struct AppTraits<Apps::StopWatch> {
static constexpr Apps app = Apps::StopWatch;
static constexpr const char* icon = Screens::Symbols::stopWatch;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::StopWatch(*controllers.systemTask);
};
};
}
} }

View file

@ -1,5 +1,4 @@
#include "displayapp/screens/Tile.h" #include "displayapp/screens/Tile.h"
#include "displayapp/DisplayApp.h"
#include "displayapp/screens/BatteryIcon.h" #include "displayapp/screens/BatteryIcon.h"
#include "components/ble/BleController.h" #include "components/ble/BleController.h"
#include "displayapp/InfiniTimeTheme.h" #include "displayapp/InfiniTimeTheme.h"

View file

@ -62,7 +62,7 @@ Timer::Timer(Controllers::Timer& timerController) : timer {timerController} {
txtPlayPause = lv_label_create(lv_scr_act(), nullptr); txtPlayPause = lv_label_create(lv_scr_act(), nullptr);
lv_obj_align(txtPlayPause, btnPlayPause, LV_ALIGN_CENTER, 0, 0); lv_obj_align(txtPlayPause, btnPlayPause, LV_ALIGN_CENTER, 0, 0);
if (timerController.IsRunning()) { if (timer.IsRunning()) {
SetTimerRunning(); SetTimerRunning();
} else { } else {
SetTimerStopped(); SetTimerStopped();

View file

@ -8,38 +8,51 @@
#include <lvgl/lvgl.h> #include <lvgl/lvgl.h>
#include "components/timer/Timer.h" #include "components/timer/Timer.h"
#include "Symbols.h"
namespace Pinetime::Applications::Screens { namespace Pinetime::Applications {
class Timer : public Screen { namespace Screens {
public: class Timer : public Screen {
Timer(Controllers::Timer& timerController); public:
~Timer() override; Timer(Controllers::Timer& timerController);
void Refresh() override; ~Timer() override;
void Reset(); void Refresh() override;
void ToggleRunning(); void Reset();
void ButtonPressed(); void ToggleRunning();
void MaskReset(); void ButtonPressed();
void MaskReset();
private: private:
void SetTimerRunning(); void SetTimerRunning();
void SetTimerStopped(); void SetTimerStopped();
void UpdateMask(); void UpdateMask();
Controllers::Timer& timer; Pinetime::Controllers::Timer& timer;
lv_obj_t* btnPlayPause; lv_obj_t* btnPlayPause;
lv_obj_t* txtPlayPause; lv_obj_t* txtPlayPause;
lv_obj_t* btnObjectMask; lv_obj_t* btnObjectMask;
lv_obj_t* highlightObjectMask; lv_obj_t* highlightObjectMask;
lv_objmask_mask_t* btnMask; lv_objmask_mask_t* btnMask;
lv_objmask_mask_t* highlightMask; lv_objmask_mask_t* highlightMask;
lv_task_t* taskRefresh; lv_task_t* taskRefresh;
Widgets::Counter minuteCounter = Widgets::Counter(0, 59, jetbrains_mono_76); Widgets::Counter minuteCounter = Widgets::Counter(0, 59, jetbrains_mono_76);
Widgets::Counter secondCounter = Widgets::Counter(0, 59, jetbrains_mono_76); Widgets::Counter secondCounter = Widgets::Counter(0, 59, jetbrains_mono_76);
bool buttonPressing = false; bool buttonPressing = false;
lv_coord_t maskPosition = 0; lv_coord_t maskPosition = 0;
TickType_t pressTime = 0; TickType_t pressTime = 0;
};
}
template <>
struct AppTraits<Apps::Timer> {
static constexpr Apps app = Apps::Timer;
static constexpr const char* icon = Screens::Symbols::hourGlass;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Timer(controllers.timer);
};
}; };
} }

View file

@ -1,7 +1,8 @@
#pragma once #pragma once
#include <lvgl/src/lv_core/lv_obj.h> #include "displayapp/Apps.h"
#include "displayapp/screens/Screen.h" #include "displayapp/screens/Screen.h"
#include "displayapp/Controllers.h"
namespace Pinetime { namespace Pinetime {
namespace Applications { namespace Applications {
@ -35,5 +36,15 @@ namespace Pinetime {
bool placeNewTile(); bool placeNewTile();
}; };
} }
template <>
struct AppTraits<Apps::Twos> {
static constexpr Apps app = Apps::Twos;
static constexpr const char* icon = "2";
static Screens::Screen* Create(AppControllers& /*controllers*/) {
return new Screens::Twos();
};
};
} }
} }

View file

@ -1,9 +1,12 @@
#pragma once #pragma once
#include <memory> #include <memory>
#include <components/ble/weather/WeatherService.h> #include "components/ble/weather/WeatherService.h"
#include "Screen.h" #include "Screen.h"
#include "ScreenList.h" #include "ScreenList.h"
#include "displayapp/Apps.h"
#include "displayapp/Controllers.h"
#include "Symbols.h"
namespace Pinetime { namespace Pinetime {
namespace Applications { namespace Applications {
@ -41,5 +44,15 @@ namespace Pinetime {
std::unique_ptr<Screen> CreateScreenHumidity(); std::unique_ptr<Screen> CreateScreenHumidity();
}; };
} }
template <>
struct AppTraits<Apps::Weather> {
static constexpr Apps app = Apps::Weather;
static constexpr const char* icon = Screens::Symbols::sun;
static Screens::Screen* Create(AppControllers& controllers) {
return new Screens::Weather(controllers.displayApp, *controllers.weatherController);
};
};
} }
} }

View file

@ -131,8 +131,8 @@ void SpiMaster::OnEndEvent() {
if (s > 0) { if (s > 0) {
auto currentSize = std::min((size_t) 255, s); auto currentSize = std::min((size_t) 255, s);
PrepareTx(currentBufferAddr, currentSize); PrepareTx(currentBufferAddr, currentSize);
currentBufferAddr += currentSize; currentBufferAddr = currentBufferAddr + currentSize;
currentBufferSize -= currentSize; currentBufferSize = currentBufferSize - currentSize;
spiBaseAddress->TASKS_START = 1; spiBaseAddress->TASKS_START = 1;
} else { } else {
@ -153,7 +153,7 @@ void SpiMaster::OnEndEvent() {
void SpiMaster::OnStartedEvent() { void SpiMaster::OnStartedEvent() {
} }
void SpiMaster::PrepareTx(const volatile uint32_t bufferAddress, const volatile size_t size) { void SpiMaster::PrepareTx(const uint32_t bufferAddress, const size_t size) {
spiBaseAddress->TXD.PTR = bufferAddress; spiBaseAddress->TXD.PTR = bufferAddress;
spiBaseAddress->TXD.MAXCNT = size; spiBaseAddress->TXD.MAXCNT = size;
spiBaseAddress->TXD.LIST = 0; spiBaseAddress->TXD.LIST = 0;
@ -163,7 +163,7 @@ void SpiMaster::PrepareTx(const volatile uint32_t bufferAddress, const volatile
spiBaseAddress->EVENTS_END = 0; spiBaseAddress->EVENTS_END = 0;
} }
void SpiMaster::PrepareRx(const volatile uint32_t bufferAddress, const volatile size_t size) { void SpiMaster::PrepareRx(const uint32_t bufferAddress, const size_t size) {
spiBaseAddress->TXD.PTR = 0; spiBaseAddress->TXD.PTR = 0;
spiBaseAddress->TXD.MAXCNT = 0; spiBaseAddress->TXD.MAXCNT = 0;
spiBaseAddress->TXD.LIST = 0; spiBaseAddress->TXD.LIST = 0;
@ -195,8 +195,8 @@ bool SpiMaster::Write(uint8_t pinCsn, const uint8_t* data, size_t size) {
auto currentSize = std::min((size_t) 255, (size_t) currentBufferSize); auto currentSize = std::min((size_t) 255, (size_t) currentBufferSize);
PrepareTx(currentBufferAddr, currentSize); PrepareTx(currentBufferAddr, currentSize);
currentBufferSize -= currentSize; currentBufferSize = currentBufferSize - currentSize;
currentBufferAddr += currentSize; currentBufferAddr = currentBufferAddr + currentSize;
spiBaseAddress->TASKS_START = 1; spiBaseAddress->TASKS_START = 1;
if (size == 1) { if (size == 1) {

View file

@ -41,7 +41,7 @@ namespace {
// RRED (Reload Register Enable) is a bitfield of 8 bits. Each bit represent // RRED (Reload Register Enable) is a bitfield of 8 bits. Each bit represent
// one of the eight reload registers available. // one of the eight reload registers available.
// In this case, we enable only the first one. // In this case, we enable only the first one.
NRF_WDT->RREN |= 1; NRF_WDT->RREN = NRF_WDT->RREN | 1;
} }
/// Returns the reset reason provided by the POWER subsystem /// Returns the reset reason provided by the POWER subsystem

View file

@ -3,8 +3,8 @@ find_program(LV_FONT_CONV "lv_font_conv" NO_CACHE REQUIRED
HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin") HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin")
message(STATUS "Using ${LV_FONT_CONV} to generate font files") message(STATUS "Using ${LV_FONT_CONV} to generate font files")
find_program(LV_IMG_CONV "lv_img_conv" NO_CACHE REQUIRED find_program(LV_IMG_CONV "lv_img_conv.py" NO_CACHE REQUIRED
HINTS "${CMAKE_SOURCE_DIR}/node_modules/.bin") HINTS "${CMAKE_CURRENT_SOURCE_DIR}")
message(STATUS "Using ${LV_IMG_CONV} to generate font files") message(STATUS "Using ${LV_IMG_CONV} to generate font files")
if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12) if(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)

View file

@ -11,6 +11,9 @@ import subprocess
def gen_lvconv_line(lv_img_conv: str, dest: str, color_format: str, output_format: str, binary_format: str, sources: str): def gen_lvconv_line(lv_img_conv: str, dest: str, color_format: str, output_format: str, binary_format: str, sources: str):
args = [lv_img_conv, sources, '--force', '--output-file', dest, '--color-format', color_format, '--output-format', output_format, '--binary-format', binary_format] args = [lv_img_conv, sources, '--force', '--output-file', dest, '--color-format', color_format, '--output-format', output_format, '--binary-format', binary_format]
if lv_img_conv.endswith(".py"):
# lv_img_conv is a python script, call with current python executable
args = [sys.executable] + args
return args return args

193
src/resources/lv_img_conv.py Executable file
View file

@ -0,0 +1,193 @@
#!/usr/bin/env python3
import argparse
import pathlib
import sys
import decimal
from PIL import Image
def classify_pixel(value, bits):
def round_half_up(v):
"""python3 implements "propper" "banker's rounding" by rounding to the nearest
even number. Javascript rounds to the nearest integer.
To have the same output as the original JavaScript implementation add a custom
rounding function, which does "school" rounding (to the nearest integer).
see: https://stackoverflow.com/questions/43851273/how-to-round-float-0-5-up-to-1-0-while-still-rounding-0-45-to-0-0-as-the-usual
"""
return int(decimal.Decimal(v).quantize(decimal.Decimal('1'), rounding=decimal.ROUND_HALF_UP))
tmp = 1 << (8 - bits)
val = round_half_up(value / tmp) * tmp
if val < 0:
val = 0
return val
def test_classify_pixel():
# test difference between round() and round_half_up()
assert classify_pixel(18, 5) == 16
# school rounding 4.5 to 5, but banker's rounding 4.5 to 4
assert classify_pixel(18, 6) == 20
def main():
parser = argparse.ArgumentParser()
parser.add_argument("img",
help="Path to image to convert to C header file")
parser.add_argument("-o", "--output-file",
help="output file path (for single-image conversion)",
required=True)
parser.add_argument("-f", "--force",
help="allow overwriting the output file",
action="store_true")
parser.add_argument("-i", "--image-name",
help="name of image structure (not implemented)")
parser.add_argument("-c", "--color-format",
help="color format of image",
default="CF_TRUE_COLOR_ALPHA",
choices=[
"CF_ALPHA_1_BIT", "CF_ALPHA_2_BIT", "CF_ALPHA_4_BIT",
"CF_ALPHA_8_BIT", "CF_INDEXED_1_BIT", "CF_INDEXED_2_BIT", "CF_INDEXED_4_BIT",
"CF_INDEXED_8_BIT", "CF_RAW", "CF_RAW_CHROMA", "CF_RAW_ALPHA",
"CF_TRUE_COLOR", "CF_TRUE_COLOR_ALPHA", "CF_TRUE_COLOR_CHROMA", "CF_RGB565A8",
],
required=True)
parser.add_argument("-t", "--output-format",
help="output format of image",
default="bin", # default in original is 'c'
choices=["c", "bin"])
parser.add_argument("--binary-format",
help="binary color format (needed if output-format is binary)",
default="ARGB8565_RBSWAP",
choices=["ARGB8332", "ARGB8565", "ARGB8565_RBSWAP", "ARGB8888"])
parser.add_argument("-s", "--swap-endian",
help="swap endian of image (not implemented)",
action="store_true")
parser.add_argument("-d", "--dither",
help="enable dither (not implemented)",
action="store_true")
args = parser.parse_args()
img_path = pathlib.Path(args.img)
out = pathlib.Path(args.output_file)
if not img_path.is_file():
print(f"Input file is missing: '{args.img}'")
return 1
print(f"Beginning conversion of {args.img}")
if out.exists():
if args.force:
print(f"overwriting {args.output_file}")
else:
pritn(f"Error: refusing to overwrite {args.output_file} without -f specified.")
return 1
out.touch()
# only implemented the bare minimum, everything else is not implemented
if args.color_format not in ["CF_INDEXED_1_BIT", "CF_TRUE_COLOR_ALPHA"]:
raise NotImplementedError(f"argument --color-format '{args.color_format}' not implemented")
if args.output_format != "bin":
raise NotImplementedError(f"argument --output-format '{args.output_format}' not implemented")
if args.binary_format not in ["ARGB8565_RBSWAP", "ARGB8888"]:
raise NotImplementedError(f"argument --binary-format '{args.binary_format}' not implemented")
if args.image_name:
raise NotImplementedError(f"argument --image-name not implemented")
if args.swap_endian:
raise NotImplementedError(f"argument --swap-endian not implemented")
if args.dither:
raise NotImplementedError(f"argument --dither not implemented")
# open image using Pillow
img = Image.open(img_path)
img_height = img.height
img_width = img.width
if args.color_format == "CF_TRUE_COLOR_ALPHA" and args.binary_format == "ARGB8888":
buf = bytearray(img_height*img_width*4) # 4 bytes (32 bit) per pixel
for y in range(img_height):
for x in range(img_width):
i = (y*img_width + x)*4 # buffer-index
pixel = img.getpixel((x,y))
r, g, b, a = pixel
buf[i + 0] = r
buf[i + 1] = g
buf[i + 2] = b
buf[i + 3] = a
elif args.color_format == "CF_TRUE_COLOR_ALPHA" and args.binary_format == "ARGB8565_RBSWAP":
buf = bytearray(img_height*img_width*3) # 3 bytes (24 bit) per pixel
for y in range(img_height):
for x in range(img_width):
i = (y*img_width + x)*3 # buffer-index
pixel = img.getpixel((x,y))
r_act = classify_pixel(pixel[0], 5)
g_act = classify_pixel(pixel[1], 6)
b_act = classify_pixel(pixel[2], 5)
a = pixel[3]
r_act = min(r_act, 0xF8)
g_act = min(g_act, 0xFC)
b_act = min(b_act, 0xF8)
c16 = ((r_act) << 8) | ((g_act) << 3) | ((b_act) >> 3) # RGR565
buf[i + 0] = (c16 >> 8) & 0xFF
buf[i + 1] = c16 & 0xFF
buf[i + 2] = a
elif args.color_format == "CF_INDEXED_1_BIT": # ignore binary format, use color format as binary format
w = img_width >> 3
if img_width & 0x07:
w+=1
max_p = w * (img_height-1) + ((img_width-1) >> 3) + 8 # +8 for the palette
buf = bytearray(max_p+1)
for y in range(img_height):
for x in range(img_width):
c, a = img.getpixel((x,y))
p = w * y + (x >> 3) + 8 # +8 for the palette
buf[p] |= (c & 0x1) << (7 - (x & 0x7))
# write palette information, for indexed-1-bit we need palette with two values
# write 8 palette bytes
buf[0] = 0
buf[1] = 0
buf[2] = 0
buf[3] = 0
# Normally there is much math behind this, but for the current use case this is close enough
# only needs to be more complicated if we have more than 2 colors in the palette
buf[4] = 255
buf[5] = 255
buf[6] = 255
buf[7] = 255
else:
# raise just to be sure
raise NotImplementedError(f"args.color_format '{args.color_format}' with args.binary_format '{args.binary_format}' not implemented")
# write header
match args.color_format:
case "CF_TRUE_COLOR_ALPHA":
lv_cf = 5
case "CF_INDEXED_1_BIT":
lv_cf = 7
case _:
# raise just to be sure
raise NotImplementedError(f"args.color_format '{args.color_format}' not implemented")
header_32bit = lv_cf | (img_width << 10) | (img_height << 21)
buf_out = bytearray(4 + len(buf))
buf_out[0] = header_32bit & 0xFF
buf_out[1] = (header_32bit & 0xFF00) >> 8
buf_out[2] = (header_32bit & 0xFF0000) >> 16
buf_out[3] = (header_32bit & 0xFF000000) >> 24
buf_out[4:] = buf
# write byte buffer to file
with open(out, "wb") as f:
f.write(buf_out)
return 0
if __name__ == '__main__':
if "--test" in sys.argv:
# run small set of tests and exit
print("running tests")
test_classify_pixel()
print("success!")
sys.exit(0)
# run normal program
sys.exit(main())

View file

@ -136,6 +136,9 @@ void SystemTask::Work() {
settingsController.Init(); settingsController.Init();
displayApp.Register(this); displayApp.Register(this);
displayApp.Register(&nimbleController.weather());
displayApp.Register(&nimbleController.music());
displayApp.Register(&nimbleController.navigation());
displayApp.Start(bootError); displayApp.Start(bootError);
heartRateSensor.Init(); heartRateSensor.Init();