diff --git a/.github/setMatrix.sh b/.github/setMatrix.sh new file mode 100755 index 000000000..bfd210575 --- /dev/null +++ b/.github/setMatrix.sh @@ -0,0 +1,19 @@ +if [ "$1" = "--default-lang" ]; then + # default_lang + echo $(X=$(if [ -e include/locales/en-US.h ]; then X=$(ls include/locales/en-US.h); else X=$(ls include/locales/*.h -1 | head -1); fi; echo ${X:16:5});X=($X);jq --compact-output --null-input '$ARGS.positional' --args -- "${X[@]}") +elif [ "$1" = "--default-mod" ]; then + # default_mod + echo $(X=$(X=$(cat platformio.ini | grep "defau"); echo "${X:15}");X=($X);jq --compact-output --null-input '$ARGS.positional' --args -- "${X[@]}") +elif [ "$1" = "--get-flag" ]; then + # get_flag + echo $(X=$(curl https://api.github.com/repos/Open-smartwatch/open-smartwatch.github.io/contents/docs/resources/firmware.md | jq -r ".content" | base64 --decode | grep -o '^-.*` |'); X=$(echo $X | tr '` |-' ' ');X=($X);jq --compact-output --null-input '$ARGS.positional' --args -- "${X[@]}") +elif [ "$1" = "--get-models" ]; then + # get_models + echo $(X=$(cat platformio.ini | grep "\[\env:" | sed -e 's/\[\env://' | tr ']\n' ' ');X=($X);jq --compact-output --null-input '$ARGS.positional' --args -- "${X[@]}") +elif [ "$1" = "--get-languages" ]; then + # get_languages + echo $(cd include/locales/; X=$(ls *.h -1 | sed -e 's/\.h$//' | tr '\n' ' ');X=($X);jq --compact-output --null-input '$ARGS.positional' --args -- "${X[@]}") +else + echo "Try, again ! $(date +%F)" + exit 1 +fi \ No newline at end of file diff --git a/.github/workflows/test-FEATURE.yml b/.github/workflows/test-FEATURE.yml index 4ef130d4c..6dcfce33e 100644 --- a/.github/workflows/test-FEATURE.yml +++ b/.github/workflows/test-FEATURE.yml @@ -16,13 +16,13 @@ jobs: submodules: recursive - id: get-flag run: | - echo "::set-output name=feature::$(D=$(curl https://api.github.com/repos/Open-smartwatch/open-smartwatch.github.io/contents/docs/resources/firmware.md | jq -r ".content" | base64 --decode | grep -o '^-.*` |'); V=$(echo $D | tr '` |-' ' ');V=($V);jq --compact-output --null-input '$ARGS.positional' --args -- "${V[@]}")" + echo "::set-output name=feature::$(./.github/setMatrix.sh --get-flag)" - id: default_mod run: | - echo "::set-output name=default_model::$(R=$(x=$(cat platformio.ini | grep "defau"); echo "${x:15}"); R=($R);jq --compact-output --null-input '$ARGS.positional' --args -- "${R[@]}")" + echo "::set-output name=default_model::$(./.github/setMatrix.sh --default-mod)" - id: default_lang run: | - echo "::set-output name=default_language::$(A=$(if [ -e include/locales/en-US.h ]; then s=$(ls include/locales/en-US.h); else s=$(ls include/locales/*.h -1 | head -1); fi; echo ${s:16:5});A=($A);jq --compact-output --null-input '$ARGS.positional' --args -- "${A[@]}")" + echo "::set-output name=default_language::$(./.github/setMatrix.sh --default-lang)" outputs: feature: ${{ steps.get-flag.outputs.feature }} default_model: ${{ steps.default_mod.outputs.default_model }} diff --git a/.github/workflows/test-OSW.yml b/.github/workflows/test-OSW.yml index d0eac2fe3..7ca1c03c6 100644 --- a/.github/workflows/test-OSW.yml +++ b/.github/workflows/test-OSW.yml @@ -15,9 +15,9 @@ jobs: with: submodules: recursive - id: get-languages - run: echo "::set-output name=languages_matrix::$(cd include/locales/; X=$(ls *.h -1 | sed -e 's/\.h$//' | tr '\n' ' '); X=($X); jq --compact-output --null-input '$ARGS.positional' --args -- "${X[@]}")" + run: echo "::set-output name=languages_matrix::$(./.github/setMatrix.sh --get-languages)" - id: get-models - run: echo "::set-output name=models_matrix::$(F=$(cat platformio.ini | grep "\[\env:" | sed -e 's/\[\env://' | tr ']\n' ' ');F=($F);jq --compact-output --null-input '$ARGS.positional' --args -- "${F[@]}")" + run: echo "::set-output name=models_matrix::$(./.github/setMatrix.sh --get-models)" outputs: languages_matrix: ${{ steps.get-languages.outputs.languages_matrix }} models_matrix: ${{ steps.get-models.outputs.models_matrix }} diff --git a/.github/workflows/test-emulator.yml b/.github/workflows/test-emulator.yml new file mode 100644 index 000000000..a048f7bae --- /dev/null +++ b/.github/workflows/test-emulator.yml @@ -0,0 +1,26 @@ +name: OSW-EMULATOR-test + +on: + workflow_dispatch: + push: + pull_request: + branches: [ master, develop ] + +jobs: + build-EMULATOR: + runs-on: ubuntu-22.04 + steps: + - name: Checkout repository and submodules + uses: actions/checkout@v2 + with: + submodules: recursive + - name: Update packages + run: sudo apt-get update && sudo apt-get upgrade -y + - name: Install packages + run: sudo apt-get -y install gcc g++ cmake libsdl2-dev libsdl2-image-dev + - name: Create build directory + run: mkdir build + - name: CMake (debug) + run: cd build && cmake -DCMAKE_BUILD_TYPE=Debug .. + - name: Make + run: cd build && make \ No newline at end of file diff --git a/.gitignore b/.gitignore index f355cdbce..70f521e16 100755 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ firmware.elf bin /*.png screenshots/ +build/ +emulator_nvs/ \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 8f4161bd4..b2045c3a5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "lib/lib-open-smartwatch"] - path = lib/lib-open-smartwatch - url = https://github.com/Open-Smartwatch/lib-open-smartwatch.git [submodule "lib/pngle"] path = lib/pngle url = https://github.com/kikuchan/pngle.git @@ -23,3 +20,12 @@ path = lib/LUA url = https://github.com/lua/lua.git branch = v5.3 +[submodule "emulator/lib/ArduinoJson"] + path = emulator/lib/ArduinoJson + url = https://github.com/bblanchon/ArduinoJson.git +[submodule "emulator/lib/Jzon"] + path = emulator/lib/Jzon + url = https://github.com/Zguy/Jzon.git +[submodule "emulator/lib/ImGUI"] + path = emulator/lib/ImGUI + url = https://github.com/ocornut/imgui.git diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..6812b4b40 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "GDB: Emulator", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/emulator.run", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}/build", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + } + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..c7a2992c1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,108 @@ +cmake_minimum_required (VERSION 3.10) +project (OSW-OS-Emulator) + +set(CMAKE_CXX_STANDARD 20) + +# Prepare some defines, which are normally evaluated using some Python snippets... +execute_process(COMMAND "git" "rev-parse" "--short" "HEAD" OUTPUT_VARIABLE GIT_COMMIT_HASH) +execute_process(COMMAND "git" "log" "-1" "--pretty=format:%cd" "--date=format:%Y-%m-%dT%H:%M:%S%z" OUTPUT_VARIABLE GIT_COMMIT_TIME) +execute_process(COMMAND "git" "rev-parse" "--abbrev-ref" "HEAD" OUTPUT_VARIABLE GIT_BRANCH_NAME) +string(STRIP ${GIT_COMMIT_HASH} GIT_COMMIT_HASH) +string(STRIP ${GIT_COMMIT_TIME} GIT_COMMIT_TIME) +string(STRIP ${GIT_BRANCH_NAME} GIT_BRANCH_NAME) + +# For threads +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + +# This uses pkg-config, as sdl2-image does not has any CMake bindings (and the sdl2 package via vcpkg is just broken under Linux) +INCLUDE(FindPkgConfig) +PKG_SEARCH_MODULE(SDL2 REQUIRED sdl2>=2.0.20) +PKG_SEARCH_MODULE(SDL2IMAGE REQUIRED SDL2_image) + +# Pngle +file(GLOB_RECURSE SOURCES_Pngle ./lib/pngle/*.c) +add_library(Pngle ${SOURCES_Pngle}) +target_include_directories(Pngle PUBLIC ./lib/pngle/src/) + +# ArduinoJSON +add_subdirectory(emulator/lib/ArduinoJson) + +# Jzon +add_library(Jzon emulator/lib/Jzon/Jzon.cpp) +target_include_directories(Jzon PUBLIC emulator/lib/Jzon/) + +# ImGUI +add_library(ImGUI + emulator/lib/ImGUI/imgui.cpp + emulator/lib/ImGUI/imgui_draw.cpp + emulator/lib/ImGUI/imgui_widgets.cpp + emulator/lib/ImGUI/imgui_tables.cpp + emulator/lib/ImGUI/backends/imgui_impl_sdl.cpp + emulator/lib/ImGUI/backends/imgui_impl_sdlrenderer.cpp +) +target_include_directories(ImGUI PUBLIC + emulator/lib/ImGUI/ + emulator/lib/ImGUI/backends + ${SDL2_INCLUDE_DIRS} +) +target_link_libraries(ImGUI LINK_PUBLIC + ${SDL2_LIBRARY} + ${SDL2IMAGE_LIBRARIES} +) + +# Emulator +file(GLOB_RECURSE SOURCES_OSW ./src/*.cpp) +file(GLOB_RECURSE SOURCES_OSW_EMULATOR ./emulator/src/*.cpp) +add_executable(emulator.run + ${SOURCES_OSW} + ${SOURCES_OSW_EMULATOR} +) +target_include_directories(emulator.run PUBLIC + ./emulator/include + ./include + ./lib/lib-open-smartwatch + emulator/lib/Jzon/ + ${SDL2_INCLUDE_DIRS} + ${SDL2IMAGE_INCLUDE_DIRS} +) +target_link_libraries(emulator.run LINK_PUBLIC + Pngle + ArduinoJson + Threads::Threads + Jzon + ${SDL2_LIBRARY} + ${SDL2IMAGE_LIBRARIES} + ImGUI +) +target_compile_definitions(emulator.run PUBLIC + OSW_TARGET_PLATFORM_HEADER="platform/EMULATOR.h" + OSW_EMULATOR=1 + GIT_COMMIT_HASH="${GIT_COMMIT_HASH}" + GIT_COMMIT_TIME="${GIT_COMMIT_TIME}" + GIT_BRANCH_NAME="${GIT_BRANCH_NAME}" + PIO_ENV_NAME="VIRTUAL" + $<$: + DEBUG=1 # Just for legacy reasons + > + $<$: + NDEBUG=1 + > + # Comment these as you wish... + OSW_FEATURE_STATS_STEPS +) +target_compile_options(emulator.run PUBLIC + $<$: + -O0 + -g3 + -Wall + > + $<$: + -O4 + > +) + +# Including SDL2 using the system libraries is broken under MacOS, so we have to add this path manually... +if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + link_directories(/usr/local/lib) +endif() \ No newline at end of file diff --git a/Readme.md b/Readme.md index 9b87c121b..5cfee5fe1 100644 --- a/Readme.md +++ b/Readme.md @@ -42,7 +42,7 @@ $ pio run -e pico32_GPS_EDITION -t upload Depending on the watch model. -## Debugging(CLI) +## Debugging (CLI) If you want to print out the log for debugging, following command: @@ -50,7 +50,7 @@ If you want to print out the log for debugging, following command: $ pio device monitor ``` -## Creating Screen Shots of your Apps +## Creating Screenshots of your Apps @@ -98,3 +98,40 @@ You did not rename `include/config.h.example` ### Failed to connect to ESP32: Timed out waiting for packet header You did not hold down BTN1(FLASH) and then tap the RESET button on the watch whilst platform.io was trying to connect. + +## OSW Emulator +![emulator](screenshots/emulator_demo.png) + +The OS itself can be executed as a regular program on your machine. This saves you time compiling for the watch and flashing the OS, every time you make a minor change - e.g. while developing the UI or a game, which not explicitly depend on the hardware of the watch. + +This also implies some limitations what you can do with the emulator, as we had to hack and reimplement some of the Arduino-specific libraries and their (conflicting) simplifications. This also means, that it maybe necessary to extend those extensions down the road as we (likely) missed that one specific function you try to use... :wink: + +### Build (cmake) +The emulator can be build using the `CMakeLists.txt` file - you may need to install additional libraries to be able to use it. + +Here is a small example running on "Ubuntu 22.04 LTS": +```bash +$ sudo apt install libsdl2-dev libsdl2-image-dev g++ gcc cmake make build-essential +$ mkdir build && cd build +$ cmake .. +$ make -j $(nproc) +$ ./emulator.run +``` + +You also may extend the `cmake`-command with `-DCMAKE_BUILD_TYPE=Release` to get an even faster and smaller binary. + +### With Docker +If a library is unavailable, you can still use the emulator using docker (e.g. on Ubuntu 20.04 SDL2 is too old). Proceed with a typical docker installation. Showing an application running in docker requires some additional steps: + +#### Host PC +*Tested on Ubuntu 20.04.* +```bash +$ xhost + +$ xauth list # Copy the result of the command. +$ docker run --net=host -e DISPLAY -v /tmp/.X11-unix -d --name OSW -p 22:22 -it --privileged ubuntu:22.04 +``` + +#### Docker +```bash +$ xauth add <'xauth list' command result> +``` \ No newline at end of file diff --git a/emulator/include/Arduino.h b/emulator/include/Arduino.h new file mode 100644 index 000000000..660bea130 --- /dev/null +++ b/emulator/include/Arduino.h @@ -0,0 +1,9 @@ +#include "DataTypes.h" +#include "Defines.h" + +unsigned long millis(); +long random(int howbig); +long random(int howsmall, int howbig); +void delay(long millis); +int32_t min(int32_t a, int32_t b); +int32_t max(int32_t a, int32_t b); \ No newline at end of file diff --git a/emulator/include/Arduino_G.h b/emulator/include/Arduino_G.h new file mode 100644 index 000000000..538d354f3 --- /dev/null +++ b/emulator/include/Arduino_G.h @@ -0,0 +1,24 @@ +#pragma once + +#include "DataTypes.h" + +// Code for Arduino_G was copy-pasted from Arduino_GFX + +class Arduino_G { + public: + Arduino_G(int16_t w, int16_t h): WIDTH(w), HEIGHT(h) {}; // Constructor + + // This MUST be defined by the subclass: + virtual void begin(int32_t speed = 0) = 0; + + virtual void drawBitmap(int16_t x, int16_t y, uint8_t* bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bg) = 0; + virtual void drawIndexedBitmap(int16_t x, int16_t y, uint8_t* bitmap, uint16_t* color_index, int16_t w, int16_t h) = 0; + virtual void draw3bitRGBBitmap(int16_t x, int16_t y, uint8_t* bitmap, int16_t w, int16_t h) = 0; + virtual void draw16bitRGBBitmap(int16_t x, int16_t y, uint16_t* bitmap, int16_t w, int16_t h) = 0; + virtual void draw24bitRGBBitmap(int16_t x, int16_t y, uint8_t* bitmap, int16_t w, int16_t h) = 0; + + protected: + int16_t + WIDTH, ///< This is the 'raw' display width - never changes + HEIGHT; ///< This is the 'raw' display height - never changes +}; \ No newline at end of file diff --git a/emulator/include/Arduino_GFX.h b/emulator/include/Arduino_GFX.h new file mode 100644 index 000000000..c6a4c995e --- /dev/null +++ b/emulator/include/Arduino_GFX.h @@ -0,0 +1,6 @@ +#pragma once + +class Graphics2DPrint; +typedef Graphics2DPrint Arduino_GFX; + +#include "Arduino_G.h" \ No newline at end of file diff --git a/emulator/include/CPU.h b/emulator/include/CPU.h new file mode 100644 index 000000000..e9624502d --- /dev/null +++ b/emulator/include/CPU.h @@ -0,0 +1,3 @@ +#pragma once + +void setCpuFrequencyMhz(int); \ No newline at end of file diff --git a/emulator/include/DataTypes.h b/emulator/include/DataTypes.h new file mode 100644 index 000000000..31b70453b --- /dev/null +++ b/emulator/include/DataTypes.h @@ -0,0 +1,4 @@ +#include +#include + +#define RTC_DATA_ATTR \ No newline at end of file diff --git a/emulator/include/Defines.h b/emulator/include/Defines.h new file mode 100644 index 000000000..9dbea92b4 --- /dev/null +++ b/emulator/include/Defines.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +#define PI 3.1415926535897932384626433832795 +#define HALF_PI 1.5707963267948966192313216916398 +#define TWO_PI 6.283185307179586476925286766559 +#define DEG_TO_RAD 0.017453292519943295769236907684886 +#define RAD_TO_DEG 57.295779513082320876798154814105 +#define EULER 2.718281828459045235360287471352 + +#define OSW_EMULATOR_THIS_IS_NOT_IMPLEMENTED std::cerr << "FIXME: " << __FILE__ << ":" << __LINE__ << "->" << __FUNCTION__ << "() Not implemented!" << std::endl; \ No newline at end of file diff --git a/emulator/include/Display.h b/emulator/include/Display.h new file mode 100644 index 000000000..31a452cfd --- /dev/null +++ b/emulator/include/Display.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include + +#include "Arduino_GFX.h" +#include "Defines.h" + +class FakeDisplay : public Arduino_G { + public: + FakeDisplay(int width, int height, SDL_Window* window, SDL_Renderer* renderer); + ~FakeDisplay(); + + void displayOn(); + void displayOff(); + + void drawPixel(int32_t x, int32_t y, uint16_t color); + + // Required by Arduino_G + void begin(int32_t speed = 0) override; + void drawBitmap(int16_t x, int16_t y, uint8_t* bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bg) override; + void drawIndexedBitmap(int16_t x, int16_t y, uint8_t* bitmap, uint16_t* color_index, int16_t w, int16_t h) override; + void draw3bitRGBBitmap(int16_t x, int16_t y, uint8_t* bitmap, int16_t w, int16_t h) override; + void draw16bitRGBBitmap(int16_t x, int16_t y, uint16_t* bitmap, int16_t w, int16_t h) override; + void draw24bitRGBBitmap(int16_t x, int16_t y, uint8_t* bitmap, int16_t w, int16_t h) override; + private: + SDL_Window* mainWindow; + SDL_Renderer* mainRenderer; + const int width; + const int height; + bool isEnabled = false; +}; + +extern std::unique_ptr fakeDisplayInstance; \ No newline at end of file diff --git a/emulator/include/ESP.h b/emulator/include/ESP.h new file mode 100644 index 000000000..bcd95dddf --- /dev/null +++ b/emulator/include/ESP.h @@ -0,0 +1,16 @@ +#pragma once + +#include "Emulator.hpp" +#include "Defines.h" + +class ESP_t { + public: + void restart() { + OswEmulator::instance->enterSleep(true); + throw OswEmulator::EmulatorSleep(); + } +}; + +extern ESP_t ESP; + +#define ESP_OK true \ No newline at end of file diff --git a/emulator/include/Emulator.hpp b/emulator/include/Emulator.hpp new file mode 100644 index 000000000..fe7ac92d4 --- /dev/null +++ b/emulator/include/Emulator.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Jzon.h" + +void setup(); +void loop(); + +class OswEmulator { + public: + class EmulatorSleep { + // This is a dummy class so the execution of the loop() function can be instantly aborted whenever the emulator enters "sleep" mode + }; + + static OswEmulator* instance; // "Singleton" + + OswEmulator(); + ~OswEmulator(); + + void run(); + void exit(); + + void setButton(unsigned id, bool state); + bool getButton(unsigned id); + uint8_t getBatteryRaw(); + bool isCharging(); + + void reboot(); + void enterSleep(bool toDeepSleep); + bool fromDeepSleep(); + private: + enum class CPUState { + active, + lightSpleep, + deepSleep + }; + + SDL_Window* mainWindow = nullptr; // Do not delete() this, this is done by SDL2 + SDL_Renderer* mainRenderer = nullptr; + std::atomic_bool running = true; + std::array buttons; // TODO This length should come from the platform itself! + uint8_t batRaw = 0; + bool charging = true; + CPUState cpustate = CPUState::deepSleep; + bool autoWakeUp = true; + bool wakeUpNow = false; + std::vector, short>> configValuesCache; + std::map> configSectionsToIdCache; + + // ImGui and window style / sizes + const float guiPadding = 10; + const float guiWidth = 256; + + // Timings + std::array timesLoop; + std::array timesFrames; + + std::string configPath = "config.json"; + Jzon::Node config; + + void renderGUIFrame(); + void addGUIHelp(const char* msg); +}; \ No newline at end of file diff --git a/emulator/include/FakeMe.h b/emulator/include/FakeMe.h new file mode 100644 index 000000000..4d66a61ec --- /dev/null +++ b/emulator/include/FakeMe.h @@ -0,0 +1,17 @@ +#pragma once + +#include "Defines.h" + +#include "DataTypes.h" +#include "String.h" +#include "Serial.h" +#include "IO.h" +#include "Display.h" +#include "CPU.h" +#include "ESP.h" + +void sleep(int); +int rtc_get_reset_reason(int); +void randomSeed(long l); +void esp_deep_sleep_start(); +void esp_light_sleep_start(); \ No newline at end of file diff --git a/emulator/include/FakePrint.h b/emulator/include/FakePrint.h new file mode 100644 index 000000000..ae7f71e57 --- /dev/null +++ b/emulator/include/FakePrint.h @@ -0,0 +1,100 @@ +/* + Print.h - Base class that provides print() and println() + Copyright (c) 2008 David A. Mellis. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef Print_h +#define Print_h + +#include +#include +#include +#include + +// don't support the arduino specifics +// #include "Printable.h" +// #include "WString.h" + +#define DEC 10 +#define HEX 16 +#define OCT 8 +#define BIN 2 + +class Print { + private: + int write_error; + size_t printNumber(unsigned long, uint8_t); + size_t printFloat(double, uint8_t); + + protected: + void setWriteError(int err = 1) { + write_error = err; + } + + public: + Print() : write_error(0) {} + virtual ~Print() {} + int getWriteError() { + return write_error; + } + void clearWriteError() { + setWriteError(0); + } + + virtual size_t write(uint8_t) = 0; + size_t write(const char* str) { + if (str == NULL) { + return 0; + } + return write((const uint8_t*)str, strlen(str)); + } + virtual size_t write(const uint8_t* buffer, size_t size); + size_t write(const char* buffer, size_t size) { + return write((const uint8_t*)buffer, size); + } + + size_t printf(const char* format, ...) __attribute__((format(printf, 2, 3))); +// size_t print(const __FlashStringHelper *); +// size_t print(const String &); + size_t print(const char[]); + size_t print(char); + size_t print(unsigned char, int = DEC); + size_t print(int, int = DEC); + size_t print(unsigned int, int = DEC); + size_t print(long, int = DEC); + size_t print(unsigned long, int = DEC); + size_t print(double, int = 2); + size_t print(const std::string); +// size_t print(const Printable &); + size_t print(struct tm* timeinfo, const char* format = NULL); + +// size_t println(const __FlashStringHelper *); +// size_t println(const String &s); + size_t println(const char[]); + size_t println(char); + size_t println(unsigned char, int = DEC); + size_t println(int, int = DEC); + size_t println(unsigned int, int = DEC); + size_t println(long, int = DEC); + size_t println(unsigned long, int = DEC); + size_t println(double, int = 2); + size_t println(const std::string); +// size_t println(const Printable &); + size_t println(struct tm* timeinfo, const char* format = NULL); + size_t println(void); +}; + +#endif \ No newline at end of file diff --git a/emulator/include/Fakegfxfont.h b/emulator/include/Fakegfxfont.h new file mode 100644 index 000000000..ec1abe846 --- /dev/null +++ b/emulator/include/Fakegfxfont.h @@ -0,0 +1,28 @@ +// Font structures for newer Adafruit_GFX (1.1 and later). +// Example fonts are included in 'Fonts' directory. +// To use a font in your Arduino sketch, #include the corresponding .h +// file and pass address of GFXfont struct to setFont(). Pass NULL to +// revert to 'classic' fixed-space bitmap font. +#ifndef _GFXFONT_H_ +#define _GFXFONT_H_ + +/// Font data stored PER GLYPH +typedef struct { + uint16_t bitmapOffset; ///< Pointer into GFXfont->bitmap + uint8_t width; ///< Bitmap dimensions in pixels + uint8_t height; ///< Bitmap dimensions in pixels + uint8_t xAdvance; ///< Distance to advance cursor (x axis) + int8_t xOffset; ///< X dist from cursor pos to UL corner + int8_t yOffset; ///< Y dist from cursor pos to UL corner +} GFXglyph; + +/// Data stored for FONT AS A WHOLE +typedef struct { + uint8_t* bitmap; ///< Glyph bitmaps, concatenated + GFXglyph* glyph; ///< Glyph array + uint8_t first; ///< ASCII extents (first char) + uint8_t last; ///< ASCII extents (last char) + uint8_t yAdvance; ///< Newline distance (y axis) +} GFXfont; + +#endif // _GFXFONT_H_ \ No newline at end of file diff --git a/emulator/include/Fakeglcdfont.h b/emulator/include/Fakeglcdfont.h new file mode 100644 index 000000000..f08c8fd0d --- /dev/null +++ b/emulator/include/Fakeglcdfont.h @@ -0,0 +1,280 @@ +// This is the 'classic' fixed-space bitmap font for Adafruit_GFX since 1.0. +// See gfxfont.h for newer custom bitmap font info. + +#ifndef FONT5X7_H +#define FONT5X7_H + +#ifdef __AVR__ +#include +#include +#elif defined(ESP8266) +#include +#elif defined(__IMXRT1052__) || defined(__IMXRT1062__) +// PROGMEM is defefind for T4 to place data in specific memory section +#undef PROGMEM +#define PROGMEM +#else +#define PROGMEM +#endif + +// Standard ASCII 5x7 font + +static const unsigned char font[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3E, 0x5B, 0x4F, 0x5B, 0x3E, + 0x3E, 0x6B, 0x4F, 0x6B, 0x3E, + 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, + 0x18, 0x3C, 0x7E, 0x3C, 0x18, + 0x1C, 0x57, 0x7D, 0x57, 0x1C, + 0x1C, 0x5E, 0x7F, 0x5E, 0x1C, + 0x00, 0x18, 0x3C, 0x18, 0x00, + 0xFF, 0xE7, 0xC3, 0xE7, 0xFF, + 0x00, 0x18, 0x24, 0x18, 0x00, + 0xFF, 0xE7, 0xDB, 0xE7, 0xFF, + 0x30, 0x48, 0x3A, 0x06, 0x0E, + 0x26, 0x29, 0x79, 0x29, 0x26, + 0x40, 0x7F, 0x05, 0x05, 0x07, + 0x40, 0x7F, 0x05, 0x25, 0x3F, + 0x5A, 0x3C, 0xE7, 0x3C, 0x5A, + 0x7F, 0x3E, 0x1C, 0x1C, 0x08, + 0x08, 0x1C, 0x1C, 0x3E, 0x7F, + 0x14, 0x22, 0x7F, 0x22, 0x14, + 0x5F, 0x5F, 0x00, 0x5F, 0x5F, + 0x06, 0x09, 0x7F, 0x01, 0x7F, + 0x00, 0x66, 0x89, 0x95, 0x6A, + 0x60, 0x60, 0x60, 0x60, 0x60, + 0x94, 0xA2, 0xFF, 0xA2, 0x94, + 0x08, 0x04, 0x7E, 0x04, 0x08, + 0x10, 0x20, 0x7E, 0x20, 0x10, + 0x08, 0x08, 0x2A, 0x1C, 0x08, + 0x08, 0x1C, 0x2A, 0x08, 0x08, + 0x1E, 0x10, 0x10, 0x10, 0x10, + 0x0C, 0x1E, 0x0C, 0x1E, 0x0C, + 0x30, 0x38, 0x3E, 0x38, 0x30, + 0x06, 0x0E, 0x3E, 0x0E, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x5F, 0x00, 0x00, + 0x00, 0x07, 0x00, 0x07, 0x00, + 0x14, 0x7F, 0x14, 0x7F, 0x14, + 0x24, 0x2A, 0x7F, 0x2A, 0x12, + 0x23, 0x13, 0x08, 0x64, 0x62, + 0x36, 0x49, 0x56, 0x20, 0x50, + 0x00, 0x08, 0x07, 0x03, 0x00, + 0x00, 0x1C, 0x22, 0x41, 0x00, + 0x00, 0x41, 0x22, 0x1C, 0x00, + 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, + 0x08, 0x08, 0x3E, 0x08, 0x08, + 0x00, 0x80, 0x70, 0x30, 0x00, + 0x08, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x00, 0x60, 0x60, 0x00, + 0x20, 0x10, 0x08, 0x04, 0x02, + 0x3E, 0x51, 0x49, 0x45, 0x3E, + 0x00, 0x42, 0x7F, 0x40, 0x00, + 0x72, 0x49, 0x49, 0x49, 0x46, + 0x21, 0x41, 0x49, 0x4D, 0x33, + 0x18, 0x14, 0x12, 0x7F, 0x10, + 0x27, 0x45, 0x45, 0x45, 0x39, + 0x3C, 0x4A, 0x49, 0x49, 0x31, + 0x41, 0x21, 0x11, 0x09, 0x07, + 0x36, 0x49, 0x49, 0x49, 0x36, + 0x46, 0x49, 0x49, 0x29, 0x1E, + 0x00, 0x00, 0x14, 0x00, 0x00, + 0x00, 0x40, 0x34, 0x00, 0x00, + 0x00, 0x08, 0x14, 0x22, 0x41, + 0x14, 0x14, 0x14, 0x14, 0x14, + 0x00, 0x41, 0x22, 0x14, 0x08, + 0x02, 0x01, 0x59, 0x09, 0x06, + 0x3E, 0x41, 0x5D, 0x59, 0x4E, + 0x7C, 0x12, 0x11, 0x12, 0x7C, + 0x7F, 0x49, 0x49, 0x49, 0x36, + 0x3E, 0x41, 0x41, 0x41, 0x22, + 0x7F, 0x41, 0x41, 0x41, 0x3E, + 0x7F, 0x49, 0x49, 0x49, 0x41, + 0x7F, 0x09, 0x09, 0x09, 0x01, + 0x3E, 0x41, 0x41, 0x51, 0x73, + 0x7F, 0x08, 0x08, 0x08, 0x7F, + 0x00, 0x41, 0x7F, 0x41, 0x00, + 0x20, 0x40, 0x41, 0x3F, 0x01, + 0x7F, 0x08, 0x14, 0x22, 0x41, + 0x7F, 0x40, 0x40, 0x40, 0x40, + 0x7F, 0x02, 0x1C, 0x02, 0x7F, + 0x7F, 0x04, 0x08, 0x10, 0x7F, + 0x3E, 0x41, 0x41, 0x41, 0x3E, + 0x7F, 0x09, 0x09, 0x09, 0x06, + 0x3E, 0x41, 0x51, 0x21, 0x5E, + 0x7F, 0x09, 0x19, 0x29, 0x46, + 0x26, 0x49, 0x49, 0x49, 0x32, + 0x03, 0x01, 0x7F, 0x01, 0x03, + 0x3F, 0x40, 0x40, 0x40, 0x3F, + 0x1F, 0x20, 0x40, 0x20, 0x1F, + 0x3F, 0x40, 0x38, 0x40, 0x3F, + 0x63, 0x14, 0x08, 0x14, 0x63, + 0x03, 0x04, 0x78, 0x04, 0x03, + 0x61, 0x59, 0x49, 0x4D, 0x43, + 0x00, 0x7F, 0x41, 0x41, 0x41, + 0x02, 0x04, 0x08, 0x10, 0x20, + 0x00, 0x41, 0x41, 0x41, 0x7F, + 0x04, 0x02, 0x01, 0x02, 0x04, + 0x40, 0x40, 0x40, 0x40, 0x40, + 0x00, 0x03, 0x07, 0x08, 0x00, + 0x20, 0x54, 0x54, 0x78, 0x40, + 0x7F, 0x28, 0x44, 0x44, 0x38, + 0x38, 0x44, 0x44, 0x44, 0x28, + 0x38, 0x44, 0x44, 0x28, 0x7F, + 0x38, 0x54, 0x54, 0x54, 0x18, + 0x00, 0x08, 0x7E, 0x09, 0x02, + 0x18, 0xA4, 0xA4, 0x9C, 0x78, + 0x7F, 0x08, 0x04, 0x04, 0x78, + 0x00, 0x44, 0x7D, 0x40, 0x00, + 0x20, 0x40, 0x40, 0x3D, 0x00, + 0x7F, 0x10, 0x28, 0x44, 0x00, + 0x00, 0x41, 0x7F, 0x40, 0x00, + 0x7C, 0x04, 0x78, 0x04, 0x78, + 0x7C, 0x08, 0x04, 0x04, 0x78, + 0x38, 0x44, 0x44, 0x44, 0x38, + 0xFC, 0x18, 0x24, 0x24, 0x18, + 0x18, 0x24, 0x24, 0x18, 0xFC, + 0x7C, 0x08, 0x04, 0x04, 0x08, + 0x48, 0x54, 0x54, 0x54, 0x24, + 0x04, 0x04, 0x3F, 0x44, 0x24, + 0x3C, 0x40, 0x40, 0x20, 0x7C, + 0x1C, 0x20, 0x40, 0x20, 0x1C, + 0x3C, 0x40, 0x30, 0x40, 0x3C, + 0x44, 0x28, 0x10, 0x28, 0x44, + 0x4C, 0x90, 0x90, 0x90, 0x7C, + 0x44, 0x64, 0x54, 0x4C, 0x44, + 0x00, 0x08, 0x36, 0x41, 0x00, + 0x00, 0x00, 0x77, 0x00, 0x00, + 0x00, 0x41, 0x36, 0x08, 0x00, + 0x02, 0x01, 0x02, 0x04, 0x02, + 0x3C, 0x26, 0x23, 0x26, 0x3C, + 0x1E, 0xA1, 0xA1, 0x61, 0x12, + 0x3A, 0x40, 0x40, 0x20, 0x7A, + 0x38, 0x54, 0x54, 0x55, 0x59, + 0x21, 0x55, 0x55, 0x79, 0x41, + 0x22, 0x54, 0x54, 0x78, 0x42, // a-umlaut + 0x21, 0x55, 0x54, 0x78, 0x40, + 0x20, 0x54, 0x55, 0x79, 0x40, + 0x0C, 0x1E, 0x52, 0x72, 0x12, + 0x39, 0x55, 0x55, 0x55, 0x59, + 0x39, 0x54, 0x54, 0x54, 0x59, + 0x39, 0x55, 0x54, 0x54, 0x58, + 0x00, 0x00, 0x45, 0x7C, 0x41, + 0x00, 0x02, 0x45, 0x7D, 0x42, + 0x00, 0x01, 0x45, 0x7C, 0x40, + 0x7D, 0x12, 0x11, 0x12, 0x7D, // A-umlaut + 0xF0, 0x28, 0x25, 0x28, 0xF0, + 0x7C, 0x54, 0x55, 0x45, 0x00, + 0x20, 0x54, 0x54, 0x7C, 0x54, + 0x7C, 0x0A, 0x09, 0x7F, 0x49, + 0x32, 0x49, 0x49, 0x49, 0x32, + 0x3A, 0x44, 0x44, 0x44, 0x3A, // o-umlaut + 0x32, 0x4A, 0x48, 0x48, 0x30, + 0x3A, 0x41, 0x41, 0x21, 0x7A, + 0x3A, 0x42, 0x40, 0x20, 0x78, + 0x00, 0x9D, 0xA0, 0xA0, 0x7D, + 0x3D, 0x42, 0x42, 0x42, 0x3D, // O-umlaut + 0x3D, 0x40, 0x40, 0x40, 0x3D, + 0x3C, 0x24, 0xFF, 0x24, 0x24, + 0x48, 0x7E, 0x49, 0x43, 0x66, + 0x2B, 0x2F, 0xFC, 0x2F, 0x2B, + 0xFF, 0x09, 0x29, 0xF6, 0x20, + 0xC0, 0x88, 0x7E, 0x09, 0x03, + 0x20, 0x54, 0x54, 0x79, 0x41, + 0x00, 0x00, 0x44, 0x7D, 0x41, + 0x30, 0x48, 0x48, 0x4A, 0x32, + 0x38, 0x40, 0x40, 0x22, 0x7A, + 0x00, 0x7A, 0x0A, 0x0A, 0x72, + 0x7D, 0x0D, 0x19, 0x31, 0x7D, + 0x26, 0x29, 0x29, 0x2F, 0x28, + 0x26, 0x29, 0x29, 0x29, 0x26, + 0x30, 0x48, 0x4D, 0x40, 0x20, + 0x38, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x38, + 0x2F, 0x10, 0xC8, 0xAC, 0xBA, + 0x2F, 0x10, 0x28, 0x34, 0xFA, + 0x00, 0x00, 0x7B, 0x00, 0x00, + 0x08, 0x14, 0x2A, 0x14, 0x22, + 0x22, 0x14, 0x2A, 0x14, 0x08, + 0x55, 0x00, 0x55, 0x00, 0x55, // #176 (25% block) missing in old code + 0xAA, 0x55, 0xAA, 0x55, 0xAA, // 50% block + 0xFF, 0x55, 0xFF, 0x55, 0xFF, // 75% block + 0x00, 0x00, 0x00, 0xFF, 0x00, + 0x10, 0x10, 0x10, 0xFF, 0x00, + 0x14, 0x14, 0x14, 0xFF, 0x00, + 0x10, 0x10, 0xFF, 0x00, 0xFF, + 0x10, 0x10, 0xF0, 0x10, 0xF0, + 0x14, 0x14, 0x14, 0xFC, 0x00, + 0x14, 0x14, 0xF7, 0x00, 0xFF, + 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x14, 0x14, 0xF4, 0x04, 0xFC, + 0x14, 0x14, 0x17, 0x10, 0x1F, + 0x10, 0x10, 0x1F, 0x10, 0x1F, + 0x14, 0x14, 0x14, 0x1F, 0x00, + 0x10, 0x10, 0x10, 0xF0, 0x00, + 0x00, 0x00, 0x00, 0x1F, 0x10, + 0x10, 0x10, 0x10, 0x1F, 0x10, + 0x10, 0x10, 0x10, 0xF0, 0x10, + 0x00, 0x00, 0x00, 0xFF, 0x10, + 0x10, 0x10, 0x10, 0x10, 0x10, + 0x10, 0x10, 0x10, 0xFF, 0x10, + 0x00, 0x00, 0x00, 0xFF, 0x14, + 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x1F, 0x10, 0x17, + 0x00, 0x00, 0xFC, 0x04, 0xF4, + 0x14, 0x14, 0x17, 0x10, 0x17, + 0x14, 0x14, 0xF4, 0x04, 0xF4, + 0x00, 0x00, 0xFF, 0x00, 0xF7, + 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0xF7, 0x00, 0xF7, + 0x14, 0x14, 0x14, 0x17, 0x14, + 0x10, 0x10, 0x1F, 0x10, 0x1F, + 0x14, 0x14, 0x14, 0xF4, 0x14, + 0x10, 0x10, 0xF0, 0x10, 0xF0, + 0x00, 0x00, 0x1F, 0x10, 0x1F, + 0x00, 0x00, 0x00, 0x1F, 0x14, + 0x00, 0x00, 0x00, 0xFC, 0x14, + 0x00, 0x00, 0xF0, 0x10, 0xF0, + 0x10, 0x10, 0xFF, 0x10, 0xFF, + 0x14, 0x14, 0x14, 0xFF, 0x14, + 0x10, 0x10, 0x10, 0x1F, 0x00, + 0x00, 0x00, 0x00, 0xF0, 0x10, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, + 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0x38, 0x44, 0x44, 0x38, 0x44, + 0xFC, 0x4A, 0x4A, 0x4A, 0x34, // sharp-s or beta + 0x7E, 0x02, 0x02, 0x06, 0x06, + 0x02, 0x7E, 0x02, 0x7E, 0x02, + 0x63, 0x55, 0x49, 0x41, 0x63, + 0x38, 0x44, 0x44, 0x3C, 0x04, + 0x40, 0x7E, 0x20, 0x1E, 0x20, + 0x06, 0x02, 0x7E, 0x02, 0x02, + 0x99, 0xA5, 0xE7, 0xA5, 0x99, + 0x1C, 0x2A, 0x49, 0x2A, 0x1C, + 0x4C, 0x72, 0x01, 0x72, 0x4C, + 0x30, 0x4A, 0x4D, 0x4D, 0x30, + 0x30, 0x48, 0x78, 0x48, 0x30, + 0xBC, 0x62, 0x5A, 0x46, 0x3D, + 0x3E, 0x49, 0x49, 0x49, 0x00, + 0x7E, 0x01, 0x01, 0x01, 0x7E, + 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, + 0x44, 0x44, 0x5F, 0x44, 0x44, + 0x40, 0x51, 0x4A, 0x44, 0x40, + 0x40, 0x44, 0x4A, 0x51, 0x40, + 0x00, 0x00, 0xFF, 0x01, 0x03, + 0xE0, 0x80, 0xFF, 0x00, 0x00, + 0x08, 0x08, 0x6B, 0x6B, 0x08, + 0x36, 0x12, 0x36, 0x24, 0x36, + 0x06, 0x0F, 0x09, 0x0F, 0x06, + 0x00, 0x00, 0x18, 0x18, 0x00, + 0x00, 0x00, 0x10, 0x10, 0x00, + 0x30, 0x40, 0xFF, 0x01, 0x01, + 0x00, 0x1F, 0x01, 0x01, 0x1E, + 0x00, 0x19, 0x1D, 0x17, 0x12, + 0x00, 0x3C, 0x3C, 0x3C, 0x3C, + 0x00, 0x00, 0x00, 0x00, 0x00 // #255 NBSP +}; +#endif // FONT5X7_H \ No newline at end of file diff --git a/emulator/include/Fakepgmspace.h b/emulator/include/Fakepgmspace.h new file mode 100644 index 000000000..1e718740a --- /dev/null +++ b/emulator/include/Fakepgmspace.h @@ -0,0 +1,7 @@ +// this is copy and paste form the ESP IDF + +#define pgm_read_byte(addr) (*(const unsigned char *)(addr)) +#define pgm_read_word(addr) ({ \ + typeof(addr) _addr = (addr); \ + *(const unsigned short *)(_addr); \ +}) diff --git a/emulator/include/IO.h b/emulator/include/IO.h new file mode 100644 index 000000000..fc2f7731a --- /dev/null +++ b/emulator/include/IO.h @@ -0,0 +1,22 @@ +#pragma once + +#include "DataTypes.h" + +#define INPUT 0 +#define OUTPUT 0 + +#define LOW 0 +#define HIGH 255 + +#define GPIO_NUM_0 255 // TODO FIX THIS!!! +#define GPIO_NUM_34 255 // TODO FIX THIS!!! + +#define esp_sleep_enable_ext0_wakeup(...) +#define esp_sleep_enable_ext1_wakeup(...) +#define esp_sleep_enable_timer_wakeup(...) + +void pinMode(int, int); +uint8_t digitalRead(int); +uint8_t analogRead(int); +void digitalWrite(int, uint8_t); +void ledcWrite(int, int); \ No newline at end of file diff --git a/emulator/include/Preferences.h b/emulator/include/Preferences.h new file mode 100644 index 000000000..eb99affb4 --- /dev/null +++ b/emulator/include/Preferences.h @@ -0,0 +1,103 @@ +#pragma once + +#include +#include + +#include "Jzon.h" + +#include "DataTypes.h" +#include "String.h" +#include "Defines.h" + +#include "nvs_flash.h" + +class Preferences { + public: + static const char* preferencesFolderName; + + Preferences() {}; + ~Preferences() {}; + + bool begin(const char* name, bool); + void end() {}; + +#define _PUT_DATA(F, T) inline size_t F(const char* key, T value) { \ + if(this->isKey(key)) \ + this->node.remove(key); \ + this->node.add(key, value); \ + this->serialize(); \ + return sizeof(T); \ + }; +#define _PUT_DATA_TYPED(F, T1, T2) inline size_t F(const char* key, T1 value) { \ + if(this->isKey(key)) \ + this->node.remove(key); \ + this->node.add(key, (T2) value); \ + this->serialize(); \ + return sizeof(T1); \ + }; + + _PUT_DATA(putChar, int8_t) + _PUT_DATA(putUChar, uint8_t) + _PUT_DATA(putShort, int16_t) + _PUT_DATA(putUShort, uint16_t) + _PUT_DATA(putInt, int32_t) + _PUT_DATA(putUInt, uint32_t) + _PUT_DATA(putLong, int32_t) + _PUT_DATA(putULong, uint32_t) + _PUT_DATA_TYPED(putLong64, int64_t, long long int) + _PUT_DATA_TYPED(putULong64, uint64_t, unsigned long long int) + _PUT_DATA(putFloat, float_t) + _PUT_DATA(putDouble, double_t) + _PUT_DATA(putBool, bool) + _PUT_DATA(putString, const char*) + _PUT_DATA(putString, String) + +#define _GET_DATA(F1, F2, T, D) inline T F1(const char* key, T defaultValue = D) { \ + if(this->isKey(key)) \ + return (T) this->node.get(key).F2(); \ + else \ + return defaultValue; \ + }; + + _GET_DATA(getChar, toInt, int8_t, 0) + _GET_DATA(getUChar, toInt, uint8_t, 0) + _GET_DATA(getShort, toInt, int16_t, 0) + _GET_DATA(getUShort, toInt, uint16_t, 0) + _GET_DATA(getInt, toInt, int32_t, 0) + _GET_DATA(getUInt, toInt, uint32_t, 0) + _GET_DATA(getLong, toInt, int32_t, 0) + _GET_DATA(getULong, toInt, uint32_t, 0) + _GET_DATA(getLong64, toInt, int64_t, 0) + _GET_DATA(getULong64, toInt, uint64_t, 0) + _GET_DATA(getFloat, toFloat, float_t, -1.0f) + _GET_DATA(getDouble, toDouble, double_t, -1.0f) + _GET_DATA(getBool, toBool, double_t, false) + _GET_DATA(getString, toString, String, String()) + + inline size_t getString(const char* key, char* value, size_t maxLen) { + String s = this->getString(key); + strncpy(value, s.c_str(), std::min(maxLen, s.size())); + return strlen(value); + }; + + inline bool isKey(const char* key) { + return this->node.has(key); + }; + + size_t putBytes(const char* key, const void* value, size_t len); + size_t getBytes(const char* key, void* buf, size_t maxLen); + + inline size_t getBytesLength(const char* key) { + return std::filesystem::file_size(this->getBytesPath(key)); + }; + private: + std::string name; + std::filesystem::path path; + Jzon::Node node; + + inline std::filesystem::path getBytesPath(const char* name) { + return std::filesystem::path(preferencesFolderName) / (this->name + "_" + name + ".bin"); + }; + + void serialize(); +}; \ No newline at end of file diff --git a/emulator/include/RtcDS3231.h b/emulator/include/RtcDS3231.h new file mode 100644 index 000000000..d146ebb05 --- /dev/null +++ b/emulator/include/RtcDS3231.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include "time.h" + +#include "Defines.h" + +/// This class is only used for simpler time parsing - not for providing the time itself! +class RtcDateTime { + public: + RtcDateTime() {}; + virtual ~RtcDateTime() {}; + + void InitWithEpoch32Time(time_t time); + + uint32_t Hour(); + uint32_t Minute(); + uint32_t Second(); + + uint32_t DayOfWeek(); + uint32_t Day(); + uint32_t Month(); + uint32_t Year(); + private: + time_t time = 0; +}; \ No newline at end of file diff --git a/emulator/include/Serial.h b/emulator/include/Serial.h new file mode 100644 index 000000000..4f6f68a20 --- /dev/null +++ b/emulator/include/Serial.h @@ -0,0 +1,27 @@ +#pragma once + +#include + +#include "Defines.h" + +class Serial_t { + public: + Serial_t() {}; + ~Serial_t() {}; + + template + void print(T smth) { + std::cout << smth << std::flush; + } + + template + void println(T smth) { + std::cout << smth << std::endl; + } + + void begin(int b) { + std::cout << "Serial initialized with " << b << " bauds." << std::endl; + } +}; + +extern Serial_t Serial; \ No newline at end of file diff --git a/emulator/include/String.h b/emulator/include/String.h new file mode 100644 index 000000000..c7dfee577 --- /dev/null +++ b/emulator/include/String.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include + +#include "Defines.h" + +class StringSumHelper; + +class String : public std::string { + public: + String() : std::string() {}; + String(const char* str) : std::string(str) {}; + +#define _FAKE_STR_CONSTR(T) String(T smth) : std::string(std::to_string(smth)) { }; + + _FAKE_STR_CONSTR(float) + _FAKE_STR_CONSTR(double) + _FAKE_STR_CONSTR(bool) + _FAKE_STR_CONSTR(int) + _FAKE_STR_CONSTR(unsigned int) + _FAKE_STR_CONSTR(short int) + _FAKE_STR_CONSTR(short unsigned int) + _FAKE_STR_CONSTR(long unsigned int) + _FAKE_STR_CONSTR(long int) + _FAKE_STR_CONSTR(char) + _FAKE_STR_CONSTR(unsigned char) + +#define _FAKE_STR_CPY_CONSTR(T) String(T smth) : std::string(smth) {}; + _FAKE_STR_CPY_CONSTR(std::string) + + ~String() {}; + + // For ArduinoJSON + size_t write(uint8_t c) { + this->append(std::to_string(c)); + return this->size(); + } + size_t write(const uint8_t* s, size_t n) { + for(size_t i = 0; i < n; i++) + this->append(std::to_string(s[n])); + return this->size(); + } + + int toInt() const { + return strtol(this->c_str(), nullptr, 10); + } + + float toFloat() const { + return strtof(this->c_str(), nullptr); + } + + double toDouble() const { + return strtod(this->c_str(), nullptr); + } + + friend class StringSumHelper; + friend StringSumHelper& operator + (const StringSumHelper& lhs, const String& rhs); + friend StringSumHelper& operator + (const StringSumHelper& lhs, const char* cstr); + friend StringSumHelper& operator + (const StringSumHelper& lhs, char c); + friend StringSumHelper& operator + (const StringSumHelper& lhs, unsigned char num); + friend StringSumHelper& operator + (const StringSumHelper& lhs, int num); + friend StringSumHelper& operator + (const StringSumHelper& lhs, unsigned int num); + friend StringSumHelper& operator + (const StringSumHelper& lhs, long num); + friend StringSumHelper& operator + (const StringSumHelper& lhs, unsigned long num); + friend StringSumHelper& operator + (const StringSumHelper& lhs, float num); + friend StringSumHelper& operator + (const StringSumHelper& lhs, double num); +}; + +class StringSumHelper : public String { + public: + StringSumHelper(const String& s) : String(s) {} + StringSumHelper(const char* p) : String(p) {} + StringSumHelper(char c) : String(c) {} + StringSumHelper(unsigned char num) : String(num) {} + StringSumHelper(int num) : String(num) {} + StringSumHelper(unsigned int num) : String(num) {} + StringSumHelper(long num) : String(num) {} + StringSumHelper(unsigned long num) : String(num) {} + StringSumHelper(float num) : String(num) {} + StringSumHelper(double num) : String(num) {} +}; + +// For ArduinoJSON +inline bool convertToJson(const String& t, ArduinoJson::JsonVariant variant) { + return variant.set(t.c_str()); +} \ No newline at end of file diff --git a/emulator/include/WString.h b/emulator/include/WString.h new file mode 100644 index 000000000..e69de29bb diff --git a/emulator/include/Wire.h b/emulator/include/Wire.h new file mode 100644 index 000000000..e69de29bb diff --git a/emulator/include/esp_adc_cal.h b/emulator/include/esp_adc_cal.h new file mode 100644 index 000000000..e69de29bb diff --git a/emulator/include/esp_task_wdt.h b/emulator/include/esp_task_wdt.h new file mode 100644 index 000000000..e69de29bb diff --git a/emulator/include/nvs_flash.h b/emulator/include/nvs_flash.h new file mode 100644 index 000000000..a086fc7c3 --- /dev/null +++ b/emulator/include/nvs_flash.h @@ -0,0 +1,4 @@ +#pragma once + +bool nvs_flash_erase(); +bool nvs_flash_init(); \ No newline at end of file diff --git a/emulator/lib/ArduinoJson b/emulator/lib/ArduinoJson new file mode 160000 index 000000000..6f57cda0b --- /dev/null +++ b/emulator/lib/ArduinoJson @@ -0,0 +1 @@ +Subproject commit 6f57cda0b44e288b63a7d09985f38e1e28810ae7 diff --git a/emulator/lib/ImGUI b/emulator/lib/ImGUI new file mode 160000 index 000000000..9e7c0f985 --- /dev/null +++ b/emulator/lib/ImGUI @@ -0,0 +1 @@ +Subproject commit 9e7c0f985f9dde58c2b98aa1d6b10dbfb5ce322e diff --git a/emulator/lib/Jzon b/emulator/lib/Jzon new file mode 160000 index 000000000..91b09881b --- /dev/null +++ b/emulator/lib/Jzon @@ -0,0 +1 @@ +Subproject commit 91b09881bd35e4c55d6f7c31aeebe166d1653686 diff --git a/emulator/src/Arduino.cpp b/emulator/src/Arduino.cpp new file mode 100644 index 000000000..2bf0dae5a --- /dev/null +++ b/emulator/src/Arduino.cpp @@ -0,0 +1,53 @@ +#include "Arduino.h" + +#include +#include +#include +#include +#include + +std::mt19937_64 gen(std::random_device{}()); + +unsigned long millis() { + auto duration = std::chrono::system_clock::now().time_since_epoch(); + return std::chrono::duration_cast(duration).count(); +} + +long random(int howbig) { + uint32_t x = gen(); + uint64_t m = uint64_t(x) * uint64_t(howbig); + int32_t l = int32_t(m); + if (l < howbig) { + int32_t t = -howbig; + if (t >= howbig) { + t -= howbig; + if (t >= howbig) t %= howbig; + } + while (l < t) { + x = rand(); + m = uint64_t(x) * uint64_t(howbig); + l = uint32_t(m); + } + } + return m >> 32; +} + +long random(int howsmall, int howbig) { + if (howsmall >= howbig) { + return howsmall; + } + long diff = howbig - howsmall; + return random(diff) + howsmall; +} + +void delay(long millis) { + std::this_thread::sleep_for(std::chrono::milliseconds(millis)); +} + +int32_t min(int32_t a, int32_t b) { + return a < b ? a : b; +} + +int32_t max(int32_t a, int32_t b) { + return a > b ? a : b; +} \ No newline at end of file diff --git a/emulator/src/CPU.cpp b/emulator/src/CPU.cpp new file mode 100644 index 000000000..728d4399a --- /dev/null +++ b/emulator/src/CPU.cpp @@ -0,0 +1,7 @@ +#include "../include/CPU.h" +#include "../include/Defines.h" + +void setCpuFrequencyMhz(int) { + // I mean... How? And why?! + OSW_EMULATOR_THIS_IS_NOT_IMPLEMENTED; +} \ No newline at end of file diff --git a/emulator/src/Display.cpp b/emulator/src/Display.cpp new file mode 100644 index 000000000..68228af06 --- /dev/null +++ b/emulator/src/Display.cpp @@ -0,0 +1,53 @@ +#include "../include/Display.h" + +std::unique_ptr fakeDisplayInstance; + +FakeDisplay::FakeDisplay(int width, int height, SDL_Window* window, SDL_Renderer* renderer) : Arduino_G(width, height), mainWindow(window), mainRenderer(renderer), width(width), height(height) { }; + +FakeDisplay::~FakeDisplay() { + +} + +void FakeDisplay::begin(int32_t speed) { + // Nothing to do... +} + +void FakeDisplay::drawBitmap(int16_t x, int16_t y, uint8_t* bitmap, int16_t w, int16_t h, uint16_t color, uint16_t bg) { + OSW_EMULATOR_THIS_IS_NOT_IMPLEMENTED +} + +void FakeDisplay::drawIndexedBitmap(int16_t x, int16_t y, uint8_t* bitmap, uint16_t* color_index, int16_t w, int16_t h) { + OSW_EMULATOR_THIS_IS_NOT_IMPLEMENTED +} + +void FakeDisplay::draw3bitRGBBitmap(int16_t x, int16_t y, uint8_t* bitmap, int16_t w, int16_t h) { + OSW_EMULATOR_THIS_IS_NOT_IMPLEMENTED +} + +void FakeDisplay::draw24bitRGBBitmap(int16_t x, int16_t y, uint8_t* bitmap, int16_t w, int16_t h) { + OSW_EMULATOR_THIS_IS_NOT_IMPLEMENTED +} + +void FakeDisplay::drawPixel(int32_t x, int32_t y, uint16_t color) { + // color is in rgb565 (r=5 bit, g=6 bit, b=5 bit) + const Uint8 r = ((color >> 8) & 0b11111000); + const Uint8 g = ((color >> 3) & 0b11111100); + const Uint8 b = ((color << 3)); + SDL_SetRenderDrawColor(this->mainRenderer, r, g, b, SDL_ALPHA_OPAQUE); + SDL_RenderDrawPoint(this->mainRenderer, x, y); +} + +void FakeDisplay::draw16bitRGBBitmap(int16_t x, int16_t y, uint16_t* bitmap, int16_t w, int16_t h) { + // On a real CPU we can just iterate over the givenbitmap and render every pixel by itself + for(int16_t currW = 0; currW < w; ++currW) + for(int16_t currH = 0; currH < h; ++currH) + this->drawPixel(x + currW, y + currH, bitmap[currH * w + currW]); +} + +void FakeDisplay::displayOn() { + this->isEnabled = true; +} + +void FakeDisplay::displayOff() { + this->isEnabled = false; +} \ No newline at end of file diff --git a/emulator/src/ESP.cpp b/emulator/src/ESP.cpp new file mode 100644 index 000000000..679444cea --- /dev/null +++ b/emulator/src/ESP.cpp @@ -0,0 +1,3 @@ +#include "../include/ESP.h" + +ESP_t ESP; \ No newline at end of file diff --git a/emulator/src/Emulator.cpp b/emulator/src/Emulator.cpp new file mode 100644 index 000000000..1d916dbe0 --- /dev/null +++ b/emulator/src/Emulator.cpp @@ -0,0 +1,345 @@ +#include +#include + +#include "imgui.h" +#include "imgui_impl_sdl.h" +#include "imgui_impl_sdlrenderer.h" + +#include "../../include/config_defaults.h" // For the display size + +#include "../include/Display.h" +#include "../include/Emulator.hpp" + +#include "osw_ui.h" +#include "osw_config.h" +#include "osw_config_keys.h" + +OswEmulator* OswEmulator::instance = nullptr; + +OswEmulator::OswEmulator() { + // Load emulator config + this->config = Jzon::object(); + if(std::filesystem::exists(this->configPath)) { + std::ifstream configStream(this->configPath, std::ios::in); + this->config = Jzon::Parser().parseStream(configStream); + configStream.close(); + } + + // Reset the config to default values (so we don't have to specify them on every time) + this->autoWakeUp = this->config.get("autoWakeUp").toBool(true); + + // Init the SDL window and renderer + this->mainWindow = SDL_CreateWindow( + "OSW-OS Emulator", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + this->config.get("window").get("width").toInt(DISP_W + this->guiPadding + this->guiWidth + this->guiPadding), + this->config.get("window").get("height").toInt(DISP_H), + SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI + ); + assert(this->mainWindow && "Never fail window creation"); + this->mainRenderer = SDL_CreateRenderer(this->mainWindow, -1, SDL_RENDERER_PRESENTVSYNC | SDL_RENDERER_ACCELERATED); + assert(this->mainRenderer && "Never fail renderer creation"); + fakeDisplayInstance = std::make_unique(DISP_W, DISP_H, this->mainWindow, this->mainRenderer); + + // Create ImGUI context and initialize + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableSetMousePos; + ImGui::StyleColorsDark(); + ImGui_ImplSDL2_InitForSDLRenderer(this->mainWindow, this->mainRenderer); + ImGui_ImplSDLRenderer_Init(this->mainRenderer); +} + +OswEmulator::~OswEmulator() { + // Shutdown ImGUI + ImGui_ImplSDLRenderer_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + + // Close window and renderer + SDL_DestroyRenderer(this->mainRenderer); + int w = 0, h = 0; + SDL_GetWindowSize(this->mainWindow, &w, &h); + SDL_DestroyWindow(this->mainWindow); + + // Store (window) config + this->config = Jzon::object(); // Clear the current cached object + Jzon::Node window = Jzon::object(); + window.add("width", w); + window.add("height", h); + this->config.add("window", window); + this->config.add("autoWakeUp", this->autoWakeUp); + std::ofstream configStream(this->configPath, std::ios::trunc); + Jzon::Writer().writeStream(this->config, configStream); + configStream.close(); +} + +void OswEmulator::run() { + while(this->running) { + std::chrono::time_point loopStart = std::chrono::system_clock::now(); + SDL_RenderClear(this->mainRenderer); + + SDL_Event event; + while (SDL_PollEvent(&event)) { + ImGui_ImplSDL2_ProcessEvent(&event); + if(event.type == SDL_QUIT) { + this->running = false; + break; + } + } + this->renderGUIFrame(); + + // Revive system after deep sleep as needed + if(this->cpustate == CPUState::deepSleep and (this->wakeUpNow or this->autoWakeUp)) { + setup(); + this->cpustate = CPUState::active; + this->wakeUpNow = false; + OswUI::getInstance()->mEnableTargetFPS = false; // Disable FPS limiter of the UI iteself + + /** + * At the first startup - prepare the key value cache dynamically + * We must execute this after the OswConfig::setup() call, as otherwise the key are not initialized yet + */ + if(this->configValuesCache.empty()) { + this->configValuesCache.resize(oswConfigKeysCount); + for(size_t keyId = 0; keyId < oswConfigKeysCount; ++keyId) { + const OswConfigKey* key = oswConfigKeys[keyId]; + if(key->type == OswConfigKeyTypedUIType::BOOL) + this->configValuesCache[keyId] = dynamic_cast(key)->get(); + else if (key->type == OswConfigKeyTypedUIType::FLOAT) + this->configValuesCache[keyId] = dynamic_cast(key)->get(); + else if (key->type == OswConfigKeyTypedUIType::DROPDOWN) + this->configValuesCache[keyId] = dynamic_cast(key)->get(); + else if (key->type == OswConfigKeyTypedUIType::SHORT) + this->configValuesCache[keyId] = dynamic_cast(key)->get(); + else if (key->type == OswConfigKeyTypedUIType::INT) + this->configValuesCache[keyId] = dynamic_cast(key)->get(); + else if (key->type == OswConfigKeyTypedUIType::RGB) { + uint32_t color = dynamic_cast(key)->get(); + std::array rgb = { + rgb888_red(color) / 255.f, + rgb888_green(color) / 255.f, + rgb888_blue(color) / 255.f + }; + this->configValuesCache[keyId] = rgb; + } else + throw std::runtime_error(std::string("Not implemented key type in cache: ") + (char) key->type); + + // Now cache the section label to resolve it later to this keys id + std::string labelString = key->section; + if(this->configSectionsToIdCache.find(labelString) == this->configSectionsToIdCache.end()) + this->configSectionsToIdCache.insert({labelString, {}}); + this->configSectionsToIdCache.at(labelString).push_back(keyId); + } + } + } + + // Next OS step + try { + if(this->cpustate == CPUState::active) { + std::chrono::time_point start = std::chrono::system_clock::now(); + loop(); + std::chrono::time_point end = std::chrono::system_clock::now(); + for(size_t keyId = 1; keyId < this->timesLoop.size(); ++keyId) + this->timesLoop.at(this->timesLoop.size() - keyId) = this->timesLoop.at(this->timesLoop.size() - keyId - 1); + this->timesLoop.front() = std::chrono::duration_cast(end - start).count(); + } + } catch(EmulatorSleep& e) { + // Ignore it :P + } + + // Draw ImGUI content + ImGui_ImplSDLRenderer_RenderDrawData(ImGui::GetDrawData()); + + // Update the window now with the content of the display + SDL_RenderPresent(this->mainRenderer); + SDL_UpdateWindowSurface(this->mainWindow); + + // Update render FPS + std::chrono::time_point loopEnd = std::chrono::system_clock::now(); + for(size_t keyId = 1; keyId < this->timesFrames.size(); ++keyId) + this->timesFrames.at(this->timesFrames.size() - keyId) = this->timesFrames.at(this->timesFrames.size() - keyId - 1); + const float frameTime = std::chrono::duration_cast(loopEnd - loopStart).count(); + if(frameTime > 0) + this->timesFrames.front() = 1000 / frameTime; + } +} + +void OswEmulator::exit() { + this->running = false; +} + +void OswEmulator::reboot() { + this->cpustate = CPUState::deepSleep; // This is the best we can do, as we can't really reset any global variables... +} + +void OswEmulator::enterSleep(bool toDeepSleep) { + this->cpustate = toDeepSleep ? CPUState::deepSleep : CPUState::lightSpleep; +} + +void OswEmulator::setButton(unsigned id, bool state) { + this->buttons.at(id) = state; +}; + +bool OswEmulator::getButton(unsigned id) { + return this->buttons.at(id); +}; + +uint8_t OswEmulator::getBatteryRaw() { + return this->batRaw; +} + +bool OswEmulator::isCharging() { + return this->charging; +} + +bool OswEmulator::fromDeepSleep() { + return this->cpustate == CPUState::deepSleep; +} + +void OswEmulator::addGUIHelp(const char* msg) { + ImGui::SameLine(); + ImGui::TextDisabled("(?)"); + if (ImGui::IsItemHovered()) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(msg); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + +void OswEmulator::renderGUIFrame() { + // Prepare ImGUI for the next frame + ImGui_ImplSDLRenderer_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + ImGui::NewFrame(); + + // Emulator control + ImGui::Begin("Emulator"); + ImGui::Text("CPU: %s", this->cpustate == CPUState::active ? "Active" : (this->cpustate == CPUState::lightSpleep ? "Light Sleep" : "Deep Sleep")); + ImGui::PlotLines("FPS", (float*) this->timesFrames.data(), this->timesFrames.size()); + ImGui::PlotLines("loop()", (float*) this->timesLoop.data(), this->timesLoop.size()); + ImGui::Separator(); + ImGui::Checkbox("Keep-Awake", &this->autoWakeUp); + this->addGUIHelp("This will always wakeup the watch for the next frame."); + if(!this->autoWakeUp and this->cpustate != CPUState::active and ImGui::Button("Wake Up")) + this->wakeUpNow = true; + ImGui::End(); + + // Button Control + ImGui::Begin("Buttons"); + if(ImGui::Button("Button PWR")) { + this->enterSleep(true); + this->wakeUpNow = true; + } + this->addGUIHelp("This button will interrupt the power to the CPU and reset the OS (as from deep sleep)."); + ImGui::Button("Button 1"); + this->setButton(0, ImGui::IsItemActive()); + ImGui::Button("Button 2"); + this->setButton(1, ImGui::IsItemActive()); + ImGui::Button("Button 3"); + this->setButton(2, ImGui::IsItemActive()); + ImGui::End(); + + // Virtual Sensors + ImGui::Begin("Virtual Sensors"); + if(OswHal::getInstance()->devices and OswHal::getInstance()->devices->virtualDevice) { + ImGui::InputFloat("Temperature", &OswHal::getInstance()->devices->virtualDevice->values.temperature, 1, 10); + ImGui::InputFloat("Pressure", &OswHal::getInstance()->devices->virtualDevice->values.pressure, 1, 10); + ImGui::InputFloat("Humidity", &OswHal::getInstance()->devices->virtualDevice->values.humidity, 1, 10); + ImGui::InputFloat("Acceleration X", &OswHal::getInstance()->devices->virtualDevice->values.accelerationX, 0.1f, 10); + ImGui::InputFloat("Acceleration Y", &OswHal::getInstance()->devices->virtualDevice->values.accelerationY, 0.1f, 10); + ImGui::InputFloat("Acceleration Z", &OswHal::getInstance()->devices->virtualDevice->values.accelerationZ, 0.1f, 10); + ImGui::InputInt("Magnetometer Azimuth", &OswHal::getInstance()->devices->virtualDevice->values.magnetometerAzimuth, 1, 10); + ImGui::InputInt("Steps", (int*) &OswHal::getInstance()->devices->virtualDevice->values.steps, 1, 10); // Warning - negative values will cause an underflow... ImGui has no conventient way of limiting the input range... + } else + ImGui::Text("The virtual sensors are only available, while the virtual device is active."); + ImGui::End(); + + ImGui::Begin("Configuration"); + if(this->configValuesCache.size()) { + for(auto& [label, keyIds] : this->configSectionsToIdCache) { + if(ImGui::CollapsingHeader(label.c_str())) + for(auto& keyId : keyIds) { + const OswConfigKey* key = oswConfigKeys[keyId]; + if(key->type == OswConfigKeyTypedUIType::BOOL) + ImGui::Checkbox(key->label, &std::get(this->configValuesCache[keyId])); + else if (key->type == OswConfigKeyTypedUIType::FLOAT) + ImGui::InputFloat(key->label, &std::get(this->configValuesCache[keyId])); + else if (key->type == OswConfigKeyTypedUIType::DROPDOWN) { + // The dropdowns communicate the possible options using the help field. That's hacky... + // Parse the help text as options list (separated by ',') + std::string optionsStr = key->help; + std::vector options = {""}; + for(const char& c: optionsStr) + if(c == ',') + options.push_back(""); + else + options.back() += c; + + // Determine the index of the current option + size_t currentOption = 0; + for(size_t optId = 0; optId < options.size(); ++optId) + if(options[optId] == std::get(this->configValuesCache[keyId])) + currentOption = optId; + + // Create the combo-box + if (ImGui::BeginCombo(key->label, std::get(this->configValuesCache[keyId]).c_str())) { + for (size_t i = 0; i < options.size(); i++) { + bool isSelected = currentOption == i; + if (ImGui::Selectable(options[i].c_str(), &isSelected)) + this->configValuesCache[keyId] = options[i]; + } + ImGui::EndCombo(); + } + } else if (key->type == OswConfigKeyTypedUIType::SHORT) + ImGui::InputInt(key->label, (int*) &std::get(this->configValuesCache[keyId])); // Brrr, range not supported + else if (key->type == OswConfigKeyTypedUIType::INT) + ImGui::InputInt(key->label, &std::get(this->configValuesCache[keyId])); + else if (key->type == OswConfigKeyTypedUIType::RGB) + ImGui::ColorEdit3(key->label, std::get>(this->configValuesCache[keyId]).data()); + else + throw std::runtime_error(std::string("Not implemented key type in view: ") + (char) key->type); + + if(key->help and key->type != OswConfigKeyTypedUIType::DROPDOWN) + // As said before, the dropdown has no "help"! + this->addGUIHelp(key->help); + } + } + + ImGui::Button("Save"); + if(ImGui::IsItemActive()) { + OswConfig::getInstance()->enableWrite(); + for(size_t keyId = 0; keyId < oswConfigKeysCount; ++keyId) { + OswConfigKey* key = oswConfigKeys[keyId]; + if(key->type == OswConfigKeyTypedUIType::BOOL) + dynamic_cast(key)->set(std::get(this->configValuesCache[keyId])); + else if (key->type == OswConfigKeyTypedUIType::FLOAT) + dynamic_cast(key)->set(std::get(this->configValuesCache[keyId])); + else if (key->type == OswConfigKeyTypedUIType::DROPDOWN) + dynamic_cast(key)->set(std::get(this->configValuesCache[keyId])); + else if (key->type == OswConfigKeyTypedUIType::SHORT) + dynamic_cast(key)->set(std::get(this->configValuesCache[keyId])); + else if (key->type == OswConfigKeyTypedUIType::INT) + dynamic_cast(key)->set(std::get(this->configValuesCache[keyId])); + else if (key->type == OswConfigKeyTypedUIType::RGB) { + std::array rgb = std::get>(this->configValuesCache[keyId]); + dynamic_cast(key)->set(rgb888(rgb[0] * 255.0f, rgb[1] * 255.0f, rgb[2] * 255.0f)); + } else + throw std::runtime_error(std::string("Not implemented key type in save: ") + (char) key->type); + } + OswConfig::getInstance()->disableWrite(); + OswConfig::getInstance()->notifyChange(); + } + } else + ImGui::Text("The configuration is not initialized yet."); + ImGui::End(); + + // Render the gui in memory + ImGui::Render(); +} \ No newline at end of file diff --git a/emulator/src/FakeMe.cpp b/emulator/src/FakeMe.cpp new file mode 100644 index 000000000..7936ef93c --- /dev/null +++ b/emulator/src/FakeMe.cpp @@ -0,0 +1,31 @@ +#include "../include/FakeMe.h" +#include "../include/Defines.h" + +#include "Emulator.hpp" +#include "osw_hal.h" + +#include +#include + +void sleep(int seconds) { + std::this_thread::sleep_for(std::chrono::seconds(seconds)); +} + +int rtc_get_reset_reason(int cpu) { + return OswEmulator::instance->fromDeepSleep() ? 5 : 1; +} + +void randomSeed(long l) { + srand(l); +} + +void esp_deep_sleep_start() { + OswEmulator::instance->enterSleep(true); + OswHal::resetInstance(); + throw OswEmulator::EmulatorSleep(); +} + +void esp_light_sleep_start() { + OswEmulator::instance->enterSleep(false); + throw OswEmulator::EmulatorSleep(); +} \ No newline at end of file diff --git a/emulator/src/FakePrint.cpp b/emulator/src/FakePrint.cpp new file mode 100644 index 000000000..362a0ec4d --- /dev/null +++ b/emulator/src/FakePrint.cpp @@ -0,0 +1,312 @@ +/* + Print.cpp - Base class that provides print() and println() + Copyright (c) 2008 David A. Mellis. All right reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + Modified 23 November 2006 by David A. Mellis + Modified December 2014 by Ivan Grokhotkov + Modified May 2015 by Michael C. Miller - ESP31B progmem support + */ + +#include +#include +#include +#include +#include +#include "FakeMe.h" + +#include "FakePrint.h" +extern "C" { +#include "time.h" +} + +// Public Methods ////////////////////////////////////////////////////////////// + +/* default implementation: may be overridden */ +size_t Print::write(const uint8_t* buffer, size_t size) { + size_t n = 0; + while(size--) { + n += write(*buffer++); + } + return n; +} + +size_t Print::printf(const char* format, ...) { + char loc_buf[64]; + char* temp = loc_buf; + va_list arg; + va_list copy; + va_start(arg, format); + va_copy(copy, arg); + int len = vsnprintf(temp, sizeof(loc_buf), format, copy); + va_end(copy); + if(len < 0) { + va_end(arg); + return 0; + }; + if(len >= (long) sizeof(loc_buf)) { + temp = (char*) malloc(len+1); + if(temp == NULL) { + va_end(arg); + return 0; + } + len = vsnprintf(temp, len+1, format, arg); + } + va_end(arg); + len = write((uint8_t*)temp, len); + if(temp != loc_buf) { + free(temp); + } + return len; +} + +// size_t Print::print(const __FlashStringHelper *ifsh) +// { +// return print(reinterpret_cast(ifsh)); +// } + +// size_t Print::print(const String &s) +// { +// return write(s.c_str(), s.length()); +// } + +size_t Print::print(const char str[]) { + return write(str); +} + +size_t Print::print(char c) { + return write(c); +} + +size_t Print::print(unsigned char b, int base) { + return print((unsigned long) b, base); +} + +size_t Print::print(int n, int base) { + return print((long) n, base); +} + +size_t Print::print(unsigned int n, int base) { + return print((unsigned long) n, base); +} + +size_t Print::print(long n, int base) { + if(base == 0) { + return write(n); + } else if(base == 10) { + if(n < 0) { + int t = print('-'); + n = -n; + return printNumber(n, 10) + t; + } + return printNumber(n, 10); + } else { + return printNumber(n, base); + } +} + +size_t Print::print(unsigned long n, int base) { + if(base == 0) { + return write(n); + } else { + return printNumber(n, base); + } +} + +size_t Print::print(double n, int digits) { + return printFloat(n, digits); +} + +// size_t Print::println(const __FlashStringHelper *ifsh) +// { +// size_t n = print(ifsh); +// n += println(); +// return n; +// } + +// size_t Print::print(const Printable& x) +// { +// return x.printTo(*this); +// } + +size_t Print::print(const std::string str) { + return this->write(str.c_str(), str.size()); +} + +size_t Print::print(struct tm* timeinfo, const char* format) { + const char* f = format; + if(!f) { + f = "%c"; + } + char buf[64]; + size_t written = strftime(buf, 64, f, timeinfo); + if(written == 0) { + return written; + } + return print(buf); +} + +size_t Print::println(void) { + return print("\r\n"); +} + +// size_t Print::println(const String &s) +// { +// size_t n = print(s); +// n += println(); +// return n; +// } + +size_t Print::println(const char c[]) { + size_t n = print(c); + n += println(); + return n; +} + +size_t Print::println(char c) { + size_t n = print(c); + n += println(); + return n; +} + +size_t Print::println(unsigned char b, int base) { + size_t n = print(b, base); + n += println(); + return n; +} + +size_t Print::println(int num, int base) { + size_t n = print(num, base); + n += println(); + return n; +} + +size_t Print::println(unsigned int num, int base) { + size_t n = print(num, base); + n += println(); + return n; +} + +size_t Print::println(long num, int base) { + size_t n = print(num, base); + n += println(); + return n; +} + +size_t Print::println(unsigned long num, int base) { + size_t n = print(num, base); + n += println(); + return n; +} + +size_t Print::println(double num, int digits) { + size_t n = print(num, digits); + n += println(); + return n; +} + +// size_t Print::println(const Printable& x) +// { +// size_t n = print(x); +// n += println(); +// return n; +// } + +size_t Print::println(const std::string str) { + size_t p = this->print(str); + this->println(); + return p; +} + +size_t Print::println(struct tm* timeinfo, const char* format) { + size_t n = print(timeinfo, format); + n += println(); + return n; +} + +// Private Methods ///////////////////////////////////////////////////////////// + +size_t Print::printNumber(unsigned long n, uint8_t base) { + char buf[8 * sizeof(long) + 1]; // Assumes 8-bit chars plus zero byte. + char* str = &buf[sizeof(buf) - 1]; + + *str = '\0'; + + // prevent crash if called with base == 1 + if(base < 2) { + base = 10; + } + + do { + unsigned long m = n; + n /= base; + char c = m - base * n; + *--str = c < 10 ? c + '0' : c + 'A' - 10; + } while(n); + + return write(str); +} + +size_t Print::printFloat(double number, uint8_t digits) { + size_t n = 0; + + if(isnan(number)) { + return print("nan"); + } + if(isinf(number)) { + return print("inf"); + } + if(number > 4294967040.0) { + return print("ovf"); // constant determined empirically + } + if(number < -4294967040.0) { + return print("ovf"); // constant determined empirically + } + + // Handle negative numbers + if(number < 0.0) { + n += print('-'); + number = -number; + } + + // Round correctly so that print(1.999, 2) prints as "2.00" + double rounding = 0.5; + for(uint8_t i = 0; i < digits; ++i) { + rounding /= 10.0; + } + + number += rounding; + + // Extract the integer part of the number and print it + unsigned long int_part = (unsigned long) number; + double remainder = number - (double) int_part; + n += print(int_part); + + // Print the decimal point, but only if there are digits beyond + if(digits > 0) { + n += print("."); + } + + // Extract digits from the remainder one at a time + while(digits-- > 0) { + remainder *= 10.0; + int toPrint = int(remainder); + n += print(toPrint); + remainder -= toPrint; + } + + return n; +} \ No newline at end of file diff --git a/emulator/src/IO.cpp b/emulator/src/IO.cpp new file mode 100644 index 000000000..fba3f07b8 --- /dev/null +++ b/emulator/src/IO.cpp @@ -0,0 +1,51 @@ +#include "../include/IO.h" +#include "../include/Defines.h" + +#include "Emulator.hpp" +#include OSW_TARGET_PLATFORM_HEADER +#include "osw_pins.h" // Used for BTN_* + +void pinMode(int pin, int mode) { + if(pin == OSW_DEVICE_TPS2115A_STATPWR) + return; + else if(pin == OSW_DEVICE_ESP32_BATLVL) + return; + else if(pin == BTN_1) + return; + else if(pin == BTN_2) + return; + else if(pin == BTN_3) + return; + OSW_EMULATOR_THIS_IS_NOT_IMPLEMENTED +} + +uint8_t digitalRead(int pin) { + const uint8_t buttonClickStates[] = BTN_STATE_ARRAY; + if(pin == BTN_1) + return ((OswEmulator::instance->getButton(0) ? LOW : HIGH) == buttonClickStates[0]) ? LOW : HIGH; + else if(pin == BTN_2) + return ((OswEmulator::instance->getButton(1) ? LOW : HIGH) == buttonClickStates[1]) ? LOW : HIGH; + else if(pin == BTN_3) + return ((OswEmulator::instance->getButton(2) ? LOW : HIGH) == buttonClickStates[2]) ? LOW : HIGH; + else if(pin == OSW_DEVICE_TPS2115A_STATPWR) + return OswEmulator::instance->isCharging() ? 1 : 0; + else if(pin == TFT_LED) + return 255; // The emulator has always full brightness for now... + OSW_EMULATOR_THIS_IS_NOT_IMPLEMENTED + return LOW; +} + +uint8_t analogRead(int pin) { + if(pin == OSW_DEVICE_ESP32_BATLVL) + return OswEmulator::instance->getBatteryRaw(); + OSW_EMULATOR_THIS_IS_NOT_IMPLEMENTED + return 0; +} + +void digitalWrite(int, uint8_t) { + OSW_EMULATOR_THIS_IS_NOT_IMPLEMENTED +} + +void ledcWrite(int, int) { + OSW_EMULATOR_THIS_IS_NOT_IMPLEMENTED +} \ No newline at end of file diff --git a/emulator/src/Preferences.cpp b/emulator/src/Preferences.cpp new file mode 100644 index 000000000..b56835cda --- /dev/null +++ b/emulator/src/Preferences.cpp @@ -0,0 +1,40 @@ +#include "../include/Preferences.h" + +const char* Preferences::preferencesFolderName = "emulator_nvs"; + +bool Preferences::begin(const char* name, bool) { + // Init "NVS" + nvs_flash_init(); + this->name = std::string(name); + // Init Jzon tree by loading (existing) file + this->path = std::filesystem::path(preferencesFolderName) / (this->name + ".json"); + if(std::filesystem::exists(this->path)) { + Jzon::Parser p; + this->node = p.parseFile(this->path.string()); + } else { + this->node = Jzon::object(); + } + return true; +} + +size_t Preferences::putBytes(const char* key, const void* value, size_t len) { + // As we can't store binary data into Jzon, we will instead write a file with an appropriate name... + std::ofstream outFile(this->getBytesPath(key), std::ios::binary); + outFile.write((const char*) value, len); + outFile.close(); + return len; +} + +size_t Preferences::getBytes(const char* key, void* buf, size_t maxLen) { + std::ifstream inFile(this->getBytesPath(key), std::ios::binary); + inFile.read((char*) buf, maxLen); + size_t readBytes = inFile.gcount(); + inFile.close(); + return readBytes; +} + +void Preferences::serialize() { + Jzon::Writer w; + w.writeFile(this->node, path.string()); + std::cout << "Written preferences of namespace " << this->name << std::endl; +} \ No newline at end of file diff --git a/emulator/src/RtcDS3231.cpp b/emulator/src/RtcDS3231.cpp new file mode 100644 index 000000000..752225110 --- /dev/null +++ b/emulator/src/RtcDS3231.cpp @@ -0,0 +1,35 @@ +#include "../include/RtcDS3231.h" + +void RtcDateTime::InitWithEpoch32Time(time_t time) { + this->time = time; +} + +uint32_t RtcDateTime::Hour() { + struct tm* parts = std::localtime(&this->time); + return parts->tm_hour; +} +uint32_t RtcDateTime::Minute() { + struct tm* parts = std::localtime(&this->time); + return parts->tm_min; +} +uint32_t RtcDateTime::Second() { + struct tm* parts = std::localtime(&this->time); + return parts->tm_sec; +} + +uint32_t RtcDateTime::DayOfWeek() { + struct tm* parts = std::localtime(&this->time); + return parts->tm_wday; +} +uint32_t RtcDateTime::Day() { + struct tm* parts = std::localtime(&this->time); + return parts->tm_mday; +} +uint32_t RtcDateTime::Month() { + struct tm* parts = std::localtime(&this->time); + return parts->tm_mon + 1; +} +uint32_t RtcDateTime::Year() { + struct tm* parts = std::localtime(&this->time); + return parts->tm_year + 1900; +} \ No newline at end of file diff --git a/emulator/src/Serial.cpp b/emulator/src/Serial.cpp new file mode 100644 index 000000000..af7171b4c --- /dev/null +++ b/emulator/src/Serial.cpp @@ -0,0 +1,3 @@ +#include "../include/Serial.h" + +Serial_t Serial; \ No newline at end of file diff --git a/emulator/src/String.cpp b/emulator/src/String.cpp new file mode 100644 index 000000000..cacd4212c --- /dev/null +++ b/emulator/src/String.cpp @@ -0,0 +1,62 @@ +#include "../include/String.h" +#include "../include/Defines.h" + +StringSumHelper& operator+(const StringSumHelper& lhs, const String& rhs) { + StringSumHelper& mLhs = const_cast(lhs); + mLhs.append(rhs.c_str()); + return mLhs; +} + +StringSumHelper& operator+(const StringSumHelper& lhs, const char* cstr) { + StringSumHelper& mLhs = const_cast(lhs); + mLhs.append(cstr); + return mLhs; +} + +StringSumHelper& operator+(const StringSumHelper& lhs, char c) { + StringSumHelper& mLhs = const_cast(lhs); + mLhs += c; + return mLhs; +} + +StringSumHelper& operator+(const StringSumHelper& lhs, unsigned char num) { + StringSumHelper& mLhs = const_cast(lhs); + mLhs.append(std::to_string(num)); + return mLhs; +} + +StringSumHelper& operator+(const StringSumHelper& lhs, int num) { + StringSumHelper& mLhs = const_cast(lhs); + mLhs.append(std::to_string(num)); + return mLhs; +} + +StringSumHelper& operator+(const StringSumHelper& lhs, unsigned int num) { + StringSumHelper& mLhs = const_cast(lhs); + mLhs.append(std::to_string(num)); + return mLhs; +} + +StringSumHelper& operator+(const StringSumHelper& lhs, long num) { + StringSumHelper& mLhs = const_cast(lhs); + mLhs.append(std::to_string(num)); + return mLhs; +} + +StringSumHelper& operator+(const StringSumHelper& lhs, unsigned long num) { + StringSumHelper& mLhs = const_cast(lhs); + mLhs.append(std::to_string(num)); + return mLhs; +} + +StringSumHelper& operator+(const StringSumHelper& lhs, float num) { + StringSumHelper& mLhs = const_cast(lhs); + mLhs.append(std::to_string(num)); + return mLhs; +} + +StringSumHelper& operator+(const StringSumHelper& lhs, double num) { + StringSumHelper& mLhs = const_cast(lhs); + mLhs.append(std::to_string(num)); + return mLhs; +} \ No newline at end of file diff --git a/emulator/src/main.cpp b/emulator/src/main.cpp new file mode 100644 index 000000000..f2a95a87b --- /dev/null +++ b/emulator/src/main.cpp @@ -0,0 +1,20 @@ +#include +#include + +#include + +#include "../include/Emulator.hpp" + +int main(int argc, char** argv) { + if(SDL_Init(SDL_INIT_EVERYTHING) != 0) + printf("error initializing SDL: %s\n", SDL_GetError()); + { + std::unique_ptr oswEmu = std::make_unique(); + OswEmulator::instance = oswEmu.get(); + oswEmu->run(); + OswEmulator::instance = nullptr; + } + SDL_Quit(); + + return EXIT_SUCCESS; +} \ No newline at end of file diff --git a/emulator/src/nvs_flash.cpp b/emulator/src/nvs_flash.cpp new file mode 100644 index 000000000..19a0dc892 --- /dev/null +++ b/emulator/src/nvs_flash.cpp @@ -0,0 +1,19 @@ +#include +#include + +#include "../include/nvs_flash.h" +#include "../include/Defines.h" + +#include "../include/Preferences.h" + +bool nvs_flash_erase() { + std::filesystem::remove_all(Preferences::preferencesFolderName); + std::cout << "Deleted NVS path: " << Preferences::preferencesFolderName << std::endl; + return true; +} + +bool nvs_flash_init() { + std::filesystem::create_directories(Preferences::preferencesFolderName); + std::cout << "Created NVS path: " << Preferences::preferencesFolderName << std::endl; + return true; +} \ No newline at end of file diff --git a/include/Arduino_Canvas_Graphics2D.h b/include/Arduino_Canvas_Graphics2D.h index eaa35eff9..71ddad2fc 100644 --- a/include/Arduino_Canvas_Graphics2D.h +++ b/include/Arduino_Canvas_Graphics2D.h @@ -4,20 +4,31 @@ #include #include -class Arduino_Canvas_Graphics2D : public Arduino_GFX { +class Arduino_Canvas_Graphics2D : public Graphics2DPrint { public: Arduino_Canvas_Graphics2D(int16_t w, int16_t h, Arduino_G* output, int16_t output_x = 0, int16_t output_y = 0); + /** + * DIFFERENCES TO THE ORIGINAL Arduino_GFX library: + * * setCursor -> setTextCursor + * * fillScreen -> fill + * * drawArc has an entirely differnt function signature + * * fillRoundRect -> fillRFrame + * * fillRect -> fillFrame + * * drawRect -> drawFrame + * * drawFastHLine, drawFastVLine are used internally, please use their implicit "non-fast" variants: drawHLine, drawVLine + * + * Why? Because our emulator needs to also use this library and as Arduino_GFX does NOT support building on non-embedded devices, + * we have copy-pasted this utility together... + */ + void begin(int32_t speed = 0); void writePixelPreclipped(int16_t x, int16_t y, uint16_t color); void writeFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color); void writeFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color); - void flush(void); - - Graphics2DPrint* getGraphics2D(void); + void flush(); protected: - Graphics2DPrint* _gfx2d; Arduino_G* _output; int16_t _output_x, _output_y; diff --git a/include/animations/README.md b/include/animations/README.md new file mode 100644 index 000000000..0cd13996b --- /dev/null +++ b/include/animations/README.md @@ -0,0 +1,3 @@ +These files were just migrated from the old OSW-lib. + +We should cleanup them at some point and maybe even create a unified `Animation` interface, to make it easier to use this animations in the future. \ No newline at end of file diff --git a/include/animations/anim_doom_fire.h b/include/animations/anim_doom_fire.h new file mode 100644 index 000000000..386025d9a --- /dev/null +++ b/include/animations/anim_doom_fire.h @@ -0,0 +1,61 @@ +#pragma once + +#include + +//#include "gfx_2d.h" +#include "gfx_2d_print.h" +#include "gfx_util.h" +// see: https://p3dt.net/post/2019/01/05/playing-with-doom.html + +class AnimDoomFire { + private: + const uint16_t doomColorMap[36] = { + rgb565(0x00, 0x00, 0x00), // #000000 + rgb565(0x1f, 0x07, 0x07), // #1f0707 + rgb565(0x2f, 0x0f, 0x07), // #2f0f07 + rgb565(0x47, 0x0f, 0x07), // #470f07 + rgb565(0x57, 0x17, 0x07), // #571707 + rgb565(0x67, 0x1f, 0x07), // #671f07 + rgb565(0x77, 0x1f, 0x07), // #771f07 + rgb565(0x8f, 0x27, 0x07), // #8f2707 + rgb565(0x9f, 0x2f, 0x07), // #9f2f07 + rgb565(0xaf, 0x3f, 0x07), // #af3f07 + rgb565(0xbf, 0x47, 0x07), // #bf4707 + rgb565(0xc7, 0x47, 0x07), // #c74707 + rgb565(0xDF, 0x4F, 0x07), // #DF4F07 + rgb565(0xDF, 0x57, 0x07), // #DF5707 + rgb565(0xDF, 0x57, 0x07), // #DF5707 + rgb565(0xD7, 0x5F, 0x07), // #D75F07 + rgb565(0xD7, 0x67, 0x0F), // #D7670F + rgb565(0xcf, 0x6f, 0x0f), // #cf6f0f + rgb565(0xcf, 0x77, 0x0f), // #cf770f + rgb565(0xcf, 0x7f, 0x0f), // #cf7f0f + rgb565(0xCF, 0x87, 0x17), // #CF8717 + rgb565(0xC7, 0x87, 0x17), // #C78717 + rgb565(0xC7, 0x8F, 0x17), // #C78F17 + rgb565(0xC7, 0x97, 0x1F), // #C7971F + rgb565(0xBF, 0x9F, 0x1F), // #BF9F1F + rgb565(0xBF, 0x9F, 0x1F), // #BF9F1F + rgb565(0xBF, 0xA7, 0x27), // #BFA727 + rgb565(0xBF, 0xA7, 0x27), // #BFA727 + rgb565(0xBF, 0xAF, 0x2F), // #BFAF2F + rgb565(0xB7, 0xAF, 0x2F), // #B7AF2F + rgb565(0xB7, 0xB7, 0x2F), // #B7B72F + rgb565(0xB7, 0xB7, 0x37), // #B7B737 + rgb565(0xCF, 0xCF, 0x6F), // #CFCF6F + rgb565(0xDF, 0xDF, 0x9F), // #DFDF9F + rgb565(0xEF, 0xEF, 0xC7), // #EFEFC7 + rgb565(0xFF, 0xFF, 0xFF) // #FFFFFF + }; + uint8_t** firePixels = nullptr; + + public: + AnimDoomFire(); + virtual ~AnimDoomFire(); + + void setupFire(uint8_t** firePixels, uint16_t w, uint16_t h); + void calcFire(uint8_t** firePixels, uint16_t w, uint16_t h,float* X,float* Y); + void mapFire(uint8_t** firePixels, uint16_t fireW, uint16_t fireH, Graphics2D* graphics2d, uint16_t offsetX, uint16_t offsetY); + + void loop(Graphics2DPrint* gfx,float* X = nullptr, float* Y = nullptr); +}; diff --git a/include/animations/anim_doom_fire_old.h b/include/animations/anim_doom_fire_old.h new file mode 100644 index 000000000..b94d06d2d --- /dev/null +++ b/include/animations/anim_doom_fire_old.h @@ -0,0 +1,15 @@ +#pragma once +#include + +#include "gfx_2d.h" +#include "gfx_util.h" +// see: https://p3dt.net/post/2019/01/05/playing-with-doom.html + + +void setupFire(uint8_t** firePixels, uint16_t w, uint16_t h); + +void calcFire(uint8_t** firePixels, uint16_t w, uint16_t h); + +void mapFire(uint8_t** firePixels, uint16_t fireW, uint16_t fireH, // + Graphics2D* graphics2d, uint16_t offsetX, uint16_t offsetY); + diff --git a/include/animations/anim_firework.h b/include/animations/anim_firework.h new file mode 100644 index 000000000..8f604a989 --- /dev/null +++ b/include/animations/anim_firework.h @@ -0,0 +1,42 @@ +#ifndef ANIM_FIREWORK_H +#define ANIM_FIREWORK_H + +#include + +class Graphics2D; + +class Particle { + public: + Particle() {} + float locationX; + float locationY; + float speedX; + float speedY; + void tick(long ms, float friction, float gravity); +}; + +class Firework { + private: + Particle* particles; + uint8_t numParticles; + + public: + Firework() { + numParticles = 53; + particles = new Particle[numParticles]; + } + ~Firework(void) { + delete[] particles; + } + + void init(uint16_t color, uint8_t radius, uint8_t rings, // + uint16_t screenWidth, uint16_t screenHeight); + void tick(long ms, uint8_t launchSpeed); + + void draw(Graphics2D* gfx, int16_t offsetX, int16_t offsetY); + uint16_t height; + uint16_t explHeight; + uint16_t color; + long age; +}; +#endif \ No newline at end of file diff --git a/include/animations/anim_matrix.h b/include/animations/anim_matrix.h new file mode 100644 index 000000000..7624772dd --- /dev/null +++ b/include/animations/anim_matrix.h @@ -0,0 +1,102 @@ +#ifndef ANIM_MATRIX_H +#define ANIM_MATRIX_H +#include "gfx_2d_print.h" + +#define MATRIX_STRING_MAX_LENGTH 16 + +class MatrixString { + public: + MatrixString(Graphics2DPrint* gfx, const char* matrixChars = "GATC", uint8_t numMatrixChars = 4, + uint8_t maxScale = 3) { + this->gfx = gfx; + this->matrixChars = matrixChars; + this->numMatrixChars = numMatrixChars; + this->maxScale = maxScale; + randomize(); + } + void randomize(void) { + length = random(MATRIX_STRING_MAX_LENGTH - 1) + 1; + x = 16 + random(gfx->getWidth() - 32); + scale = random(maxScale) + 1; + gfx->setTextSize(scale); + y = 0 - (gfx->getTextOfsetRows(length)) - random(100); + speed = random(scale) + scale; + for (uint8_t i = 0; i < length; i++) { + s[i] = matrixChars[random(numMatrixChars)]; + } + } + + uint8_t length; + char s[MATRIX_STRING_MAX_LENGTH]; + int16_t x; + int16_t y; + uint8_t scale; + int16_t speed; + const char* matrixChars; + uint8_t numMatrixChars; + uint8_t maxScale; + + Graphics2DPrint* gfx; + + void draw() { + gfx->setTextSize(scale); + gfx->setTextLeftAligned(); + gfx->setTextTopAligned(); + + for (uint8_t i = 0; i < length; i++) { + gfx->setTextCursor(x, y + gfx->getTextOfsetRows(i)); + gfx->print(s[i]); + } + + y += speed; + + if (random(100) > 75) { + s[random(length)] = matrixChars[random(numMatrixChars)]; + } + + if (y > gfx->getHeight()) { + randomize(); + } + } +}; + +// TODO: use an dim colors correctly +class AnimMatrix { + public: + AnimMatrix(Graphics2DPrint* gfx, const char* chars = "GATC", uint8_t charCount = 4, uint8_t stringCount = 32, + uint8_t maxScale = 3, uint16_t fgColor = rgb565(0, 255, 0), uint16_t maskColor = rgb565(255, 0, 0)) { + this->chars = chars; + this->stringCount = stringCount; + this->maxScale = maxScale; + this->fgColor = fgColor; + this->maskColor = maskColor; + + matrixStrings = new MatrixString*[stringCount]; + + for (uint8_t i = 0; i < stringCount; i++) { + matrixStrings[i] = new MatrixString(gfx, chars, charCount, maxScale); + } + } + + void loop(Graphics2DPrint* gfx) { + gfx->enableMask(maskColor); + // draw back to front + for (uint8_t c = 1; c <= maxScale; c++) { + gfx->setTextColor(rgb565(0, 32 + c * 64, 0), rgb565(255, 0, 0)); + for (uint8_t i = 0; i < stringCount; i++) { + if (matrixStrings[i]->scale == c) { + matrixStrings[i]->draw(); + } + } + } + } + + MatrixString** matrixStrings; + const char* chars; + uint8_t stringCount; + uint8_t maxScale; + uint16_t fgColor; + uint16_t maskColor; +}; + +#endif diff --git a/include/animations/anim_water_ripple.h b/include/animations/anim_water_ripple.h new file mode 100644 index 000000000..a0070b317 --- /dev/null +++ b/include/animations/anim_water_ripple.h @@ -0,0 +1,17 @@ +#ifndef WATER_RIPPLE_H +#define WATER_RIPPLE_H + +#include + +#include "gfx_2d.h" +#include "gfx_util.h" + +// implementation according to: +// https://web.archive.org/web/20160418004149/http://freespace.virgin.net/hugo.elias/graphics/x_water.htm + +void calcWater(int8_t* buf1, int8_t* buf2, uint16_t width, uint16_t height, float damping /* [0,1]*/); + +void mapWater(int8_t* buf, uint16_t width, uint16_t height, Graphics2D* background, Graphics2D* target, + uint16_t offsetX, uint16_t offsetY); + +#endif \ No newline at end of file diff --git a/include/apps/main/map.h b/include/apps/main/map.h index f3266efc6..884391498 100644 --- a/include/apps/main/map.h +++ b/include/apps/main/map.h @@ -24,4 +24,4 @@ class OswAppMap : public OswApp { uint8_t _sat_count; // in the above array }; -#endif +#endif \ No newline at end of file diff --git a/include/apps/tools/OswAppBLEMEdiaCtrl.h b/include/apps/tools/OswAppBLEMediaCtrl.h similarity index 67% rename from include/apps/tools/OswAppBLEMEdiaCtrl.h rename to include/apps/tools/OswAppBLEMediaCtrl.h index 0b5a89154..74ed095b2 100644 --- a/include/apps/tools/OswAppBLEMEdiaCtrl.h +++ b/include/apps/tools/OswAppBLEMediaCtrl.h @@ -4,13 +4,13 @@ #include #include "osw_app.h" -class OswAppBLEMEdiaCtrl : public OswApp { +class OswAppBLEMediaCtrl : public OswApp { public: - OswAppBLEMEdiaCtrl(void) {}; + OswAppBLEMediaCtrl(void) {}; virtual void setup() override; virtual void loop() override; virtual void stop() override; - ~OswAppBLEMEdiaCtrl() {}; + ~OswAppBLEMediaCtrl() {}; private: }; diff --git a/include/apps/tools/OswAppStepStats.h b/include/apps/tools/OswAppStepStats.h index b6929e84c..0d2f057e0 100644 --- a/include/apps/tools/OswAppStepStats.h +++ b/include/apps/tools/OswAppStepStats.h @@ -15,6 +15,7 @@ class OswAppStepStats : public OswApp { virtual void loop() override; virtual void stop() override; ~OswAppStepStats() {}; + static void drawInfoPanel(OswUI* ui, uint32_t pos, uint32_t lastWeekData, uint32_t todayData, uint32_t average, uint32_t total, const String& unit = String("")); private: void showStickChart(); diff --git a/include/apps/tools/water_level.h b/include/apps/tools/OswAppWaterLevel.h similarity index 100% rename from include/apps/tools/water_level.h rename to include/apps/tools/OswAppWaterLevel.h diff --git a/include/apps/watchfaces/OswAppWatchface.h b/include/apps/watchfaces/OswAppWatchface.h index ea8c4a18e..075055f7b 100644 --- a/include/apps/watchfaces/OswAppWatchface.h +++ b/include/apps/watchfaces/OswAppWatchface.h @@ -5,7 +5,7 @@ #include "osw_app.h" #ifdef ANIMATION -#include +#include #endif class OswAppWatchface : public OswApp { @@ -27,4 +27,4 @@ class OswAppWatchface : public OswApp { #ifdef ANIMATION AnimMatrix* matrix; #endif -}; \ No newline at end of file +}; diff --git a/include/apps/watchfaces/OswAppWatchfaceBinary.h b/include/apps/watchfaces/OswAppWatchfaceBinary.h index 521178bb2..94901d2a8 100644 --- a/include/apps/watchfaces/OswAppWatchfaceBinary.h +++ b/include/apps/watchfaces/OswAppWatchfaceBinary.h @@ -17,6 +17,6 @@ class OswAppWatchfaceBinary : public OswApp { private: uint16_t primaryColor; - void drawWatch(Graphics2D* gfx2d); + void drawWatch(); OswUI* ui; }; \ No newline at end of file diff --git a/include/apps/watchfaces/OswAppWatchfaceDigital.h b/include/apps/watchfaces/OswAppWatchfaceDigital.h index a71bb24d9..efc0992fb 100644 --- a/include/apps/watchfaces/OswAppWatchfaceDigital.h +++ b/include/apps/watchfaces/OswAppWatchfaceDigital.h @@ -20,6 +20,7 @@ class OswAppWatchfaceDigital : public OswApp { static void digitalWatch(short timeZone, uint8_t fontSize, uint8_t dateCoordY, uint8_t timeCoordY); static void timeOutput(uint32_t hour, uint32_t minute, uint32_t second, bool showSecond = true); static void dateOutput(uint32_t yearInt, uint32_t monthInt, uint32_t dayInt); + static void displayWeekDay3(const char* weekday); private: OswUI* ui; diff --git a/include/apps/watchfaces/OswAppWatchfaceMonotimer.h b/include/apps/watchfaces/OswAppWatchfaceMonotimer.h new file mode 100644 index 000000000..7bf9646e7 --- /dev/null +++ b/include/apps/watchfaces/OswAppWatchfaceMonotimer.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +#include "osw_app.h" + +class OswAppWatchfaceMonotimer : public OswApp { + public: + OswAppWatchfaceMonotimer(void) { + ui = OswUI::getInstance(); + }; + virtual void setup() override; + virtual void loop() override; + virtual void stop() override; + ~OswAppWatchfaceMonotimer() {}; + + private: + OswUI* ui; + void drawWatch(); + void drawNShiftedTicks(Graphics2D* gfx, uint8_t cx, uint8_t cy, uint8_t r1, uint8_t r2, uint8_t nTicks, float shift, uint16_t color); + void drawNShiftedMaskedTicks(Graphics2D* gfx, uint8_t cx, uint8_t cy, uint8_t r1, uint8_t r2, uint8_t nTicks, float shift, uint16_t m, uint16_t color); +}; diff --git a/include/config_defaults.h b/include/config_defaults.h index 17d946b9b..816babfc2 100644 --- a/include/config_defaults.h +++ b/include/config_defaults.h @@ -167,7 +167,7 @@ // The following settings are configureable later on using the web ui, you can still set the defaults here. #ifndef CONFIG_DATE_FORMAT -// possibilities: "yyyy.mm.dd" or "mm/dd/yyyy" +// possibilities: "mm/dd/yyyy","dd.mm.yyyy" and "yy.mm/dd" #define CONFIG_DATE_FORMAT "mm/dd/yyyy" #endif diff --git a/include/devices/bma400.h b/include/devices/bma400.h index d3bdce40d..fec0728a9 100644 --- a/include/devices/bma400.h +++ b/include/devices/bma400.h @@ -11,7 +11,7 @@ class BMA400 : public OswTemperatureProvider, public OswAccelerationProvider { virtual void setup() override; virtual void update() override; - virtual void reset() override; + virtual void reset() override {}; virtual void stop() override {}; virtual inline const char* getName() override { @@ -24,6 +24,7 @@ class BMA400 : public OswTemperatureProvider, public OswAccelerationProvider { }; // This sensor is not sooo good... virtual uint32_t getStepCount() override; + virtual void resetStepCount() override; virtual float getAccelerationX() override; virtual float getAccelerationY() override; virtual float getAccelerationZ() override; diff --git a/include/devices/interfaces/OswAccelerationProvider.h b/include/devices/interfaces/OswAccelerationProvider.h index 786b0c3cd..244d22bec 100644 --- a/include/devices/interfaces/OswAccelerationProvider.h +++ b/include/devices/interfaces/OswAccelerationProvider.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -7,6 +8,7 @@ class OswAccelerationProvider : public OswDevice { public: virtual uint32_t getStepCount() = 0; + virtual void resetStepCount() = 0; virtual float getAccelerationX() = 0; virtual float getAccelerationY() = 0; virtual float getAccelerationZ() = 0; diff --git a/include/devices/virtual.h b/include/devices/virtual.h index 135d91349..0be077e22 100644 --- a/include/devices/virtual.h +++ b/include/devices/virtual.h @@ -10,6 +10,17 @@ namespace OswDevices { class Virtual : public OswTemperatureProvider, public OswAccelerationProvider, public OswHumidityProvider, public OswMagnetometerProvider, public OswPressureProvider { public: + struct VirtualValues { + float temperature = 42.0f; + float pressure = 42.0f; + float humidity = 42.0f; + float accelerationX = 0.0f; + float accelerationY = 0.0f; + float accelerationZ = -9.81f; + int magnetometerAzimuth = 0; + uint32_t steps = 42; + } values; + Virtual(unsigned char priority = 0) : OswTemperatureProvider(), OswAccelerationProvider(), OswHumidityProvider(), OswMagnetometerProvider(), OswPressureProvider(), priority(priority) {}; virtual ~Virtual() {}; @@ -24,44 +35,47 @@ class Virtual : public OswTemperatureProvider, public OswAccelerationProvider, p }; virtual inline float getTemperature() override { - return 42.0; + return this->values.temperature; }; virtual inline unsigned char getTemperatureProviderPriority() override { return this->priority; }; virtual inline uint32_t getStepCount() override { - return 42; + return this->values.steps; + }; + virtual void resetStepCount() override { + this->values.steps = 0; }; virtual inline float getAccelerationX() override { - return 0; + return this->values.accelerationX; }; virtual inline float getAccelerationY() override { - return 0; + return this->values.accelerationY; }; virtual inline float getAccelerationZ() override { - return 0; + return this->values.accelerationZ; }; virtual inline unsigned char getAccelerationProviderPriority() override { return this->priority; }; virtual inline float getHumidity() override { - return 42; + return this->values.humidity; }; virtual inline unsigned char getHumidityProviderPriority() override { return this->priority; }; virtual inline float getPressure() override { - return 42; + return this->values.pressure; }; virtual inline unsigned char getPressureProviderPriority() override { return this->priority; }; virtual inline int getMagnetometerAzimuth() override { - return 42; + return this->values.magnetometerAzimuth; }; virtual inline unsigned char getMagnetometerProviderPriority() override { return this->priority; diff --git a/include/fonts/FreeSans11pt8b.h b/include/fonts/FreeSans11pt8b.h new file mode 100644 index 000000000..76fddac39 --- /dev/null +++ b/include/fonts/FreeSans11pt8b.h @@ -0,0 +1,519 @@ +const uint8_t FreeSans11pt8bBitmaps[] PROGMEM = { + 0xFF, 0xFF, 0xFF, 0x0F, 0xDE, 0xF7, 0xB9, 0x00, 0x0C, 0x60, 0xC4, 0x0C, + 0x47, 0xFF, 0x7F, 0xF0, 0x8C, 0x18, 0x81, 0x88, 0x19, 0x8F, 0xFE, 0xFF, + 0xE3, 0x10, 0x31, 0x03, 0x30, 0x23, 0x00, 0x08, 0x07, 0xE1, 0xFF, 0x74, + 0xEC, 0x87, 0x90, 0xF2, 0x07, 0x40, 0x7E, 0x03, 0xF0, 0x27, 0x04, 0x3C, + 0x87, 0x90, 0xFA, 0x73, 0xFE, 0x3F, 0x01, 0x00, 0x00, 0x08, 0x0F, 0x02, + 0x07, 0xE1, 0x03, 0x9C, 0x40, 0xC3, 0x20, 0x39, 0xC8, 0x07, 0xE4, 0x00, + 0xF1, 0x00, 0x00, 0x8F, 0x00, 0x27, 0xE0, 0x13, 0x9C, 0x04, 0xC3, 0x02, + 0x30, 0xC0, 0x8E, 0x70, 0x41, 0xF8, 0x10, 0x3C, 0x0F, 0x00, 0xFC, 0x0E, + 0x70, 0x61, 0x83, 0x0C, 0x0C, 0xC0, 0x7C, 0x03, 0xC0, 0x37, 0x33, 0x1D, + 0xB0, 0x7D, 0x81, 0xCC, 0x06, 0x70, 0xF9, 0xFE, 0xC7, 0xC3, 0xFF, 0x80, + 0x18, 0x8C, 0x46, 0x31, 0x18, 0xC6, 0x31, 0x8C, 0x61, 0x0C, 0x61, 0x0C, + 0x20, 0x80, 0xC2, 0x18, 0x43, 0x18, 0x43, 0x18, 0xC6, 0x31, 0x8C, 0x46, + 0x31, 0x18, 0x88, 0x00, 0x10, 0x23, 0xF8, 0x82, 0x8D, 0x80, 0x0C, 0x03, + 0x00, 0xC0, 0x30, 0xFF, 0xFF, 0xF0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xF5, + 0x80, 0xFF, 0xC0, 0xF0, 0x04, 0x30, 0x82, 0x18, 0x41, 0x04, 0x20, 0x82, + 0x18, 0x41, 0x0C, 0x20, 0x1E, 0x0F, 0xC7, 0x39, 0x86, 0xC0, 0xF0, 0x3C, + 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0x61, 0x98, 0xE3, 0xF0, 0x78, + 0x08, 0xCF, 0xFF, 0x8C, 0x63, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x3E, 0x1F, + 0xE6, 0x1F, 0x03, 0xC0, 0xC0, 0x30, 0x0C, 0x06, 0x07, 0x07, 0x83, 0x81, + 0x80, 0x40, 0x30, 0x0F, 0xFF, 0xFF, 0x3F, 0x1F, 0xEE, 0x1F, 0x03, 0xC0, + 0xC0, 0x70, 0xF8, 0x3E, 0x01, 0xC0, 0x30, 0x0F, 0x03, 0xC0, 0xD8, 0x67, + 0xF8, 0xF8, 0x03, 0x00, 0xC0, 0x70, 0x3C, 0x1B, 0x04, 0xC3, 0x31, 0x8C, + 0x43, 0x20, 0xCF, 0xFF, 0xFF, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x7F, 0x9F, + 0xE6, 0x01, 0x80, 0x60, 0x1B, 0x8D, 0xFB, 0x86, 0x00, 0xC0, 0x30, 0x0C, + 0x03, 0xC0, 0xF8, 0x67, 0xF8, 0xF8, 0x1E, 0x0F, 0xE7, 0x19, 0x83, 0xC0, + 0x30, 0x0D, 0xF3, 0xFE, 0xE1, 0xB0, 0x3C, 0x0F, 0x03, 0xC0, 0xD8, 0x67, + 0xF8, 0x78, 0xFF, 0xFF, 0xF0, 0x08, 0x06, 0x01, 0x00, 0xC0, 0x60, 0x18, + 0x0C, 0x03, 0x00, 0x80, 0x60, 0x18, 0x04, 0x03, 0x00, 0xC0, 0x1E, 0x0F, + 0xC7, 0x39, 0x86, 0x61, 0x9C, 0xE3, 0xF0, 0xFC, 0x61, 0xB0, 0x3C, 0x0F, + 0x03, 0xC0, 0xD8, 0x63, 0xF0, 0x78, 0x1E, 0x1F, 0xE6, 0x1B, 0x03, 0xC0, + 0xF0, 0x3C, 0x0D, 0x87, 0x7F, 0xCF, 0xB0, 0x0C, 0x03, 0xC1, 0x98, 0xE7, + 0xF0, 0x78, 0xF0, 0x00, 0x0F, 0xF0, 0x00, 0x3D, 0x60, 0x00, 0x20, 0x1C, + 0x0F, 0x0F, 0x07, 0x81, 0x80, 0x3C, 0x01, 0xF0, 0x07, 0x80, 0x3C, 0x00, + 0x80, 0xFF, 0xFF, 0xFC, 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, 0x1C, + 0x01, 0xE0, 0x0F, 0x00, 0x3C, 0x01, 0xC0, 0x78, 0x78, 0x3C, 0x1E, 0x02, + 0x00, 0x00, 0x3E, 0x3F, 0xB8, 0xF8, 0x3C, 0x18, 0x0C, 0x0C, 0x0E, 0x0C, + 0x0E, 0x06, 0x03, 0x00, 0x00, 0x00, 0x60, 0x30, 0x01, 0xFC, 0x00, 0x7F, + 0xF0, 0x0E, 0x07, 0x81, 0xC0, 0x1C, 0x30, 0x70, 0xE6, 0x1F, 0xE7, 0x63, + 0x8E, 0x3C, 0x30, 0xE3, 0xC6, 0x0C, 0x3C, 0x60, 0xC3, 0xC6, 0x0C, 0x7C, + 0x61, 0x86, 0xE7, 0x18, 0xE6, 0x3E, 0xFC, 0x71, 0xCF, 0x83, 0x80, 0x00, + 0x1E, 0x00, 0x00, 0xFF, 0xE0, 0x01, 0xF8, 0x00, 0x03, 0x80, 0x0E, 0x00, + 0x7C, 0x01, 0xB0, 0x0C, 0xC0, 0x31, 0x80, 0xC6, 0x06, 0x18, 0x18, 0x30, + 0x7F, 0xC3, 0xFF, 0x0C, 0x06, 0x30, 0x19, 0x80, 0x66, 0x00, 0xD8, 0x03, + 0xFF, 0x8F, 0xFC, 0xC0, 0xEC, 0x06, 0xC0, 0x6C, 0x06, 0xC0, 0xCF, 0xF8, + 0xFF, 0xCC, 0x06, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x07, 0xFF, 0xEF, 0xFC, + 0x07, 0xE0, 0x7F, 0xC3, 0x83, 0x9C, 0x07, 0x60, 0x0F, 0x00, 0x0C, 0x00, + 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0xD8, 0x03, 0x70, 0x18, 0xE0, + 0xE1, 0xFF, 0x01, 0xF0, 0xFF, 0x0F, 0xFC, 0xC0, 0xEC, 0x06, 0xC0, 0x7C, + 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x07, 0xC0, 0x6C, + 0x0E, 0xFF, 0xCF, 0xF0, 0xFF, 0xFF, 0xFF, 0x00, 0x60, 0x0C, 0x01, 0x80, + 0x30, 0x07, 0xFE, 0xFF, 0xD8, 0x03, 0x00, 0x60, 0x0C, 0x01, 0x80, 0x3F, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFC, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0xFE, + 0xFF, 0xB0, 0x0C, 0x03, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0x07, 0xE0, + 0x3F, 0xF0, 0xE0, 0x73, 0x80, 0x76, 0x00, 0x78, 0x00, 0x30, 0x00, 0x60, + 0x3F, 0xC0, 0x7F, 0x80, 0x0F, 0x80, 0x1B, 0x00, 0x37, 0x00, 0xE7, 0x07, + 0xC7, 0xFD, 0x83, 0xE1, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, + 0x03, 0xC0, 0x3F, 0xFF, 0xFF, 0xFC, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, + 0x03, 0xC0, 0x3C, 0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x80, 0xC0, 0x60, + 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, 0x80, 0xC0, 0x78, 0x3C, 0x1F, 0x1D, + 0xFC, 0x7C, 0xC0, 0x76, 0x03, 0x30, 0x31, 0x83, 0x0C, 0x30, 0x63, 0x03, + 0x30, 0x1B, 0xC0, 0xF6, 0x07, 0x18, 0x30, 0xE1, 0x83, 0x8C, 0x0C, 0x60, + 0x33, 0x01, 0xD8, 0x06, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0x01, + 0x80, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03, 0xFF, 0xFF, 0xE0, 0x0F, + 0xC0, 0x1F, 0xC0, 0x7F, 0x80, 0xFD, 0x01, 0x7B, 0x06, 0xF6, 0x0D, 0xE4, + 0x13, 0xCC, 0x67, 0x98, 0xCF, 0x11, 0x1E, 0x36, 0x3C, 0x6C, 0x78, 0x50, + 0xF0, 0xE1, 0xE1, 0xC3, 0xE0, 0x3E, 0x03, 0xF0, 0x3F, 0x03, 0xD8, 0x3D, + 0xC3, 0xCC, 0x3C, 0x63, 0xC6, 0x3C, 0x33, 0xC3, 0x3C, 0x1B, 0xC0, 0xFC, + 0x0F, 0xC0, 0x7C, 0x07, 0x07, 0xC0, 0x3F, 0xE0, 0xE0, 0xE3, 0x80, 0xE6, + 0x00, 0xD8, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC0, 0x07, 0x80, 0x0F, 0x00, + 0x1B, 0x00, 0x67, 0x01, 0xC7, 0x07, 0x07, 0xFC, 0x03, 0xE0, 0xFF, 0x9F, + 0xFB, 0x03, 0xE0, 0x3C, 0x07, 0x80, 0xF0, 0x3F, 0xFE, 0xFF, 0x98, 0x03, + 0x00, 0x60, 0x0C, 0x01, 0x80, 0x30, 0x06, 0x00, 0x07, 0xC0, 0x3F, 0xE0, + 0xE0, 0xE3, 0x80, 0xE6, 0x00, 0xD8, 0x00, 0xF0, 0x01, 0xE0, 0x03, 0xC0, + 0x07, 0x80, 0x0F, 0x00, 0x1B, 0x02, 0x67, 0x07, 0xC7, 0x07, 0x07, 0xFF, + 0x03, 0xE6, 0x00, 0x04, 0xFF, 0xC7, 0xFF, 0x30, 0x1D, 0x80, 0x6C, 0x03, + 0x60, 0x1B, 0x01, 0x9F, 0xF8, 0xFF, 0xE6, 0x03, 0xB0, 0x0D, 0x80, 0x6C, + 0x03, 0x60, 0x1B, 0x00, 0xD8, 0x07, 0x1F, 0x87, 0xFE, 0x60, 0x7C, 0x03, + 0xC0, 0x0C, 0x00, 0xF0, 0x07, 0xF0, 0x0F, 0xE0, 0x0F, 0x00, 0x3C, 0x03, + 0xC0, 0x37, 0x06, 0x7F, 0xE1, 0xF8, 0xFF, 0xFF, 0xFF, 0x06, 0x00, 0x60, + 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, + 0x06, 0x00, 0x60, 0x06, 0x00, 0x60, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, + 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, + 0xC0, 0x37, 0x0E, 0x7F, 0xC1, 0xF8, 0x60, 0x0D, 0x80, 0x66, 0x01, 0x8C, + 0x06, 0x30, 0x30, 0xC0, 0xC1, 0x83, 0x06, 0x18, 0x18, 0x60, 0x31, 0x80, + 0xCC, 0x03, 0xB0, 0x06, 0xC0, 0x1E, 0x00, 0x38, 0x00, 0xC0, 0xE0, 0x70, + 0x36, 0x07, 0x03, 0x60, 0x70, 0x76, 0x0F, 0x06, 0x30, 0xD8, 0x63, 0x0D, + 0x86, 0x31, 0xD8, 0x63, 0x18, 0x8C, 0x19, 0x8C, 0xC1, 0x98, 0xCC, 0x1B, + 0x0C, 0xC1, 0xB0, 0x78, 0x0F, 0x07, 0x80, 0xF0, 0x78, 0x0E, 0x03, 0x80, + 0xE0, 0x30, 0x60, 0x1C, 0xC0, 0xE3, 0x83, 0x06, 0x18, 0x0C, 0xE0, 0x3B, + 0x00, 0x78, 0x00, 0xE0, 0x03, 0x80, 0x1F, 0x00, 0xCC, 0x07, 0x18, 0x18, + 0x70, 0xC0, 0xC7, 0x01, 0x98, 0x07, 0xE0, 0x1D, 0x80, 0x67, 0x03, 0x0C, + 0x0C, 0x18, 0x60, 0x73, 0x80, 0xCC, 0x01, 0xE0, 0x07, 0x80, 0x0C, 0x00, + 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0xFF, 0xFF, + 0xFF, 0x00, 0x60, 0x0E, 0x01, 0xC0, 0x38, 0x03, 0x00, 0x60, 0x0E, 0x01, + 0xC0, 0x38, 0x03, 0x00, 0x60, 0x0E, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xCC, + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCF, 0xF0, 0x83, 0x04, 0x10, + 0x60, 0x82, 0x08, 0x10, 0x41, 0x06, 0x08, 0x20, 0xC1, 0xFF, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x3F, 0xF0, 0x18, 0x1C, 0x3C, 0x24, + 0x26, 0x62, 0x43, 0xC3, 0xFF, 0xF8, 0xC6, 0x30, 0x3E, 0x3F, 0xEC, 0x38, + 0x06, 0x01, 0x83, 0xE7, 0xFB, 0x86, 0xC1, 0xB0, 0xE7, 0xFD, 0xE3, 0xC0, + 0x18, 0x03, 0x00, 0x60, 0x0C, 0xF1, 0xFF, 0xBC, 0x77, 0x07, 0xC0, 0x78, + 0x0F, 0x01, 0xE0, 0x3E, 0x0F, 0xE3, 0xB7, 0xE6, 0x78, 0x1F, 0x0F, 0xE6, + 0x1F, 0x83, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xE0, 0xD8, 0x77, 0xF8, 0x7C, + 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x1E, 0xDF, 0xF6, 0x1F, 0x87, 0xC0, 0xF0, + 0x3C, 0x0F, 0x03, 0xE1, 0xD8, 0x77, 0xFC, 0x7B, 0x1E, 0x0F, 0xE6, 0x1B, + 0x03, 0xC0, 0xFF, 0xFF, 0xFF, 0x00, 0xC0, 0xD8, 0x67, 0xF8, 0x78, 0x37, + 0x66, 0xFF, 0x66, 0x66, 0x66, 0x66, 0x66, 0x1E, 0xDF, 0xF6, 0x1F, 0x87, + 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xE1, 0xD8, 0x77, 0xEC, 0x73, 0x00, 0xC0, + 0x7C, 0x19, 0xFE, 0x3E, 0x00, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xCF, 0x37, + 0xFF, 0x1F, 0x83, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, + 0x03, 0xF0, 0xFF, 0xFF, 0xFF, 0x33, 0x00, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x33, 0x33, 0x3F, 0xE0, 0xC0, 0x30, 0x0C, 0x03, 0x00, 0xC3, 0xB1, 0xCC, + 0xE3, 0x30, 0xD8, 0x3F, 0x0E, 0xE3, 0x18, 0xC3, 0x30, 0xCC, 0x1B, 0x07, + 0xFF, 0xFF, 0xFF, 0xFF, 0xCE, 0x3E, 0xDF, 0x7F, 0xE3, 0xC7, 0xC1, 0x83, + 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, + 0xC1, 0x83, 0xC1, 0x83, 0xCF, 0x37, 0xFF, 0x1F, 0x83, 0xC0, 0xF0, 0x3C, + 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0x1E, 0x1F, 0xE6, 0x1B, 0x87, + 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xE1, 0xD8, 0x67, 0xF8, 0x78, 0xCF, 0x1B, + 0xF3, 0xC7, 0x70, 0x7C, 0x07, 0x80, 0xF0, 0x1E, 0x03, 0xE0, 0xFE, 0x3B, + 0xFE, 0x67, 0x8C, 0x01, 0x80, 0x30, 0x06, 0x00, 0x1E, 0xDF, 0xF6, 0x1F, + 0x87, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xE1, 0xD8, 0x77, 0xFC, 0x7B, 0x00, + 0xC0, 0x30, 0x0C, 0x03, 0xDF, 0x7E, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, + 0x30, 0x3E, 0x3F, 0xF8, 0x78, 0x0E, 0x03, 0xE0, 0xFC, 0x07, 0x01, 0xE0, + 0xFF, 0xC7, 0xC0, 0x66, 0xFF, 0x66, 0x66, 0x66, 0x66, 0x77, 0xC0, 0xF0, + 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC1, 0xF8, 0xFF, 0xEC, + 0xF3, 0xC0, 0xEC, 0x19, 0x83, 0x30, 0xC3, 0x18, 0x63, 0x0C, 0xC0, 0xD8, + 0x1B, 0x03, 0xC0, 0x38, 0x06, 0x00, 0xC1, 0x83, 0x63, 0x86, 0x63, 0xC6, + 0x63, 0xC6, 0x62, 0xC4, 0x36, 0x4C, 0x36, 0x6C, 0x36, 0x6C, 0x34, 0x68, + 0x1C, 0x38, 0x1C, 0x38, 0x1C, 0x30, 0x60, 0xD8, 0x63, 0x30, 0x6C, 0x1E, + 0x03, 0x00, 0xE0, 0x78, 0x1B, 0x0C, 0x66, 0x19, 0x83, 0xC0, 0xD8, 0x36, + 0x0D, 0x86, 0x31, 0x8C, 0x43, 0x30, 0x6C, 0x1A, 0x07, 0x80, 0xE0, 0x30, + 0x0C, 0x03, 0x01, 0x81, 0xE0, 0x70, 0x00, 0xFF, 0xFF, 0xC0, 0xC0, 0xC0, + 0xE0, 0x60, 0x60, 0x60, 0x60, 0x60, 0x3F, 0xFF, 0xF0, 0x19, 0xCC, 0x63, + 0x18, 0xC6, 0x33, 0xB0, 0xC3, 0x18, 0xC6, 0x31, 0x8C, 0x71, 0x80, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xC0, 0xC7, 0x18, 0xC6, 0x31, 0x8C, 0x61, 0x86, + 0x66, 0x31, 0x8C, 0x63, 0x19, 0xCC, 0x00, 0x60, 0x7C, 0x63, 0xE0, 0x60, + 0xFF, 0xFF, 0xFF, 0xFF, 0xC7, 0xE3, 0xCF, 0xF3, 0xDC, 0x3B, 0xD8, 0x1B, + 0xD8, 0x1B, 0xC0, 0x1B, 0xC0, 0x3B, 0xC0, 0x33, 0xC0, 0xE3, 0xC0, 0xC3, + 0xC1, 0x83, 0xC1, 0x83, 0xC1, 0x83, 0xC0, 0x03, 0xC0, 0x03, 0xC1, 0x83, + 0xC1, 0x83, 0xFF, 0xFF, 0xFF, 0xFF, 0xF0, 0xFF, 0xFF, 0xFF, 0x04, 0x07, + 0xC3, 0xF9, 0xD7, 0xE4, 0xF1, 0x0C, 0x43, 0x10, 0xC4, 0x39, 0x37, 0x5C, + 0xFE, 0x1F, 0x01, 0x00, 0x0F, 0x83, 0xF8, 0xC3, 0xB0, 0x36, 0x06, 0xC0, + 0x1C, 0x01, 0xC0, 0xFF, 0x1F, 0xE0, 0x30, 0x06, 0x01, 0x80, 0x7C, 0x5F, + 0xFB, 0x0F, 0x03, 0xE0, 0x7F, 0xC7, 0x86, 0x30, 0x03, 0x00, 0x3F, 0xF3, + 0xFF, 0x86, 0x00, 0x30, 0x03, 0xFE, 0x3F, 0xF0, 0x60, 0x01, 0x80, 0x0F, + 0x00, 0x3F, 0xC0, 0x7C, 0xC0, 0xD0, 0x26, 0x18, 0x84, 0x33, 0x04, 0x81, + 0xE0, 0x30, 0x7F, 0x83, 0x00, 0xC1, 0xFE, 0x0C, 0x03, 0x00, 0xC0, 0x30, + 0x19, 0x80, 0xF0, 0x06, 0x00, 0x00, 0x1F, 0x87, 0xFE, 0x60, 0x7C, 0x03, + 0xC0, 0x0C, 0x00, 0xF0, 0x07, 0xF0, 0x0F, 0xE0, 0x0F, 0x00, 0x3C, 0x03, + 0xC0, 0x37, 0x06, 0x7F, 0xE1, 0xF8, 0x0E, 0x07, 0xC3, 0x18, 0xC6, 0x38, + 0x0F, 0x06, 0xE3, 0x0C, 0xC1, 0xB0, 0x3E, 0x0D, 0xE3, 0x3C, 0xC3, 0xE0, + 0x70, 0x0E, 0x61, 0x98, 0x67, 0x38, 0xFC, 0x1E, 0x00, 0x46, 0x16, 0x0E, + 0x00, 0x03, 0xE3, 0xFF, 0x87, 0x80, 0xE0, 0x3E, 0x0F, 0xC0, 0x70, 0x1E, + 0x0F, 0xFC, 0x7C, 0x07, 0xE0, 0x1F, 0xF8, 0x3C, 0x3C, 0x73, 0xCE, 0x67, + 0xE6, 0xEC, 0x37, 0xCC, 0x03, 0xC8, 0x03, 0xC8, 0x03, 0xCC, 0x03, 0xEC, + 0x37, 0x67, 0xE6, 0x73, 0xCE, 0x3C, 0x3C, 0x1F, 0xF8, 0x07, 0xE0, 0x72, + 0x20, 0x9E, 0x8A, 0x2F, 0xC0, 0xFC, 0x22, 0xCF, 0x34, 0x4C, 0xCC, 0xC8, + 0x80, 0xFF, 0xFF, 0xFC, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0xFF, 0xC0, + 0x07, 0xE0, 0x1F, 0xF8, 0x3C, 0x3C, 0x7F, 0xCE, 0x6F, 0xE6, 0xE8, 0x27, + 0xC8, 0x23, 0xCF, 0xE3, 0xCF, 0xE3, 0xC8, 0x63, 0xE8, 0x27, 0x68, 0x26, + 0x78, 0x2E, 0x3C, 0x3C, 0x1F, 0xF8, 0x07, 0xE0, 0xFF, 0xF0, 0x38, 0xFB, + 0x1C, 0x18, 0x38, 0xDF, 0x1C, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x07, 0xFE, + 0xFF, 0xC1, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x00, 0x00, 0x0F, 0xFF, 0xFF, + 0xC0, 0x7D, 0x8F, 0x18, 0x31, 0x84, 0x10, 0x7F, 0xFE, 0x7D, 0x8F, 0x18, + 0x31, 0x80, 0xC1, 0xE3, 0xC6, 0xF8, 0x19, 0x80, 0xF0, 0x06, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0x00, 0x60, 0x0E, 0x01, 0xC0, 0x38, 0x03, 0x00, 0x60, + 0x0E, 0x01, 0xC0, 0x38, 0x03, 0x00, 0x60, 0x0E, 0x00, 0xFF, 0xFF, 0xFF, + 0xC0, 0xD8, 0x1B, 0x03, 0x60, 0x6C, 0x0D, 0x81, 0xB0, 0x36, 0x06, 0xC1, + 0xDC, 0x7B, 0xFF, 0xFE, 0x3C, 0x01, 0x80, 0x30, 0x00, 0x1F, 0xDF, 0xF7, + 0xDB, 0xF6, 0xFD, 0xBF, 0x6F, 0xD9, 0xF6, 0x7D, 0x8F, 0x60, 0xD8, 0x36, + 0x0D, 0x83, 0x60, 0xD8, 0x36, 0x0D, 0x83, 0x60, 0xD8, 0xF0, 0x66, 0x16, + 0x0E, 0x00, 0x0F, 0xFF, 0xFC, 0x0C, 0x0C, 0x0E, 0x06, 0x06, 0x06, 0x06, + 0x06, 0x03, 0xFF, 0xFF, 0x3D, 0xB6, 0xDB, 0x60, 0x7B, 0x38, 0x61, 0x87, + 0x37, 0x80, 0xFC, 0x89, 0x99, 0x99, 0x16, 0x79, 0xA2, 0x00, 0x0F, 0x3F, + 0xE7, 0xFF, 0xFD, 0xC3, 0xC0, 0x30, 0x38, 0x06, 0x03, 0x01, 0x80, 0x60, + 0x30, 0x0C, 0x06, 0x01, 0xFF, 0xC0, 0x3F, 0xF8, 0x06, 0x03, 0x00, 0xC0, + 0x70, 0x18, 0x06, 0x03, 0x00, 0xE1, 0xE0, 0x0F, 0xFF, 0xF8, 0x79, 0xFF, + 0x1E, 0x1F, 0x0F, 0xE7, 0xF1, 0x87, 0xC3, 0x70, 0xF0, 0x3C, 0x0C, 0x07, + 0x81, 0xFF, 0xF0, 0x3F, 0xFE, 0x06, 0x00, 0xC0, 0xC0, 0x6C, 0x3C, 0x19, + 0xFC, 0xFF, 0x0F, 0x0F, 0x80, 0x0C, 0xC0, 0x33, 0x00, 0x00, 0x38, 0x07, + 0x60, 0x19, 0xC0, 0xC3, 0x03, 0x06, 0x18, 0x1C, 0xE0, 0x33, 0x00, 0x78, + 0x01, 0xE0, 0x03, 0x00, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, + 0x00, 0x30, 0x00, 0x0C, 0x06, 0x00, 0x00, 0x00, 0xC0, 0x60, 0x70, 0x30, + 0x70, 0x30, 0x30, 0x18, 0x3C, 0x1F, 0x1D, 0xFC, 0x7C, 0x06, 0x00, 0x0C, + 0x00, 0x18, 0x00, 0x00, 0x03, 0x80, 0x0E, 0x00, 0x7C, 0x01, 0xB0, 0x0C, + 0xC0, 0x31, 0x80, 0xC6, 0x06, 0x18, 0x18, 0x30, 0x7F, 0xC3, 0xFF, 0x0C, + 0x06, 0x30, 0x19, 0x80, 0x66, 0x00, 0xD8, 0x03, 0x00, 0xC0, 0x06, 0x00, + 0x30, 0x00, 0x00, 0x03, 0x80, 0x0E, 0x00, 0x7C, 0x01, 0xB0, 0x0C, 0xC0, + 0x31, 0x80, 0xC6, 0x06, 0x18, 0x18, 0x30, 0x7F, 0xC3, 0xFF, 0x0C, 0x06, + 0x30, 0x19, 0x80, 0x66, 0x00, 0xD8, 0x03, 0x03, 0x00, 0x1E, 0x00, 0xCC, + 0x00, 0x00, 0x03, 0x80, 0x0E, 0x00, 0x7C, 0x01, 0xB0, 0x0C, 0xC0, 0x31, + 0x80, 0xC6, 0x06, 0x18, 0x18, 0x30, 0x7F, 0xC3, 0xFF, 0x0C, 0x06, 0x30, + 0x19, 0x80, 0x66, 0x00, 0xD8, 0x03, 0x06, 0x60, 0x3F, 0x80, 0x9C, 0x00, + 0x00, 0x03, 0x80, 0x0E, 0x00, 0x7C, 0x01, 0xB0, 0x0C, 0xC0, 0x31, 0x80, + 0xC6, 0x06, 0x18, 0x18, 0x30, 0x7F, 0xC3, 0xFF, 0x0C, 0x06, 0x30, 0x19, + 0x80, 0x66, 0x00, 0xD8, 0x03, 0x06, 0x60, 0x19, 0x80, 0x00, 0x00, 0xE0, + 0x03, 0x80, 0x1F, 0x00, 0x6C, 0x03, 0x30, 0x0C, 0x60, 0x31, 0x81, 0x86, + 0x06, 0x0C, 0x1F, 0xF0, 0xFF, 0xC3, 0x01, 0x8C, 0x06, 0x60, 0x19, 0x80, + 0x36, 0x00, 0xC0, 0x03, 0xC0, 0x1F, 0x00, 0x66, 0x01, 0xB8, 0x07, 0xC0, + 0x06, 0x00, 0x38, 0x00, 0xE0, 0x07, 0xC0, 0x1B, 0x00, 0xCC, 0x03, 0x18, + 0x0C, 0x60, 0x61, 0x81, 0x83, 0x07, 0xFC, 0x3F, 0xF0, 0xC0, 0x63, 0x01, + 0x98, 0x06, 0x60, 0x0D, 0x80, 0x30, 0x03, 0xFF, 0xF0, 0x3F, 0xFF, 0x07, + 0x30, 0x00, 0x63, 0x00, 0x06, 0x30, 0x00, 0xC3, 0x00, 0x0C, 0x30, 0x01, + 0x83, 0xFE, 0x18, 0x3F, 0xE1, 0xFF, 0x00, 0x3F, 0xF0, 0x03, 0x03, 0x00, + 0x70, 0x30, 0x06, 0x03, 0x00, 0x60, 0x3F, 0xFC, 0x03, 0xFF, 0x07, 0xE0, + 0x7F, 0xC3, 0x83, 0x9C, 0x07, 0x60, 0x0F, 0x00, 0x0C, 0x00, 0x30, 0x00, + 0xC0, 0x03, 0x00, 0x0C, 0x00, 0xD8, 0x03, 0x70, 0x18, 0xE0, 0xE1, 0xFF, + 0x03, 0xF0, 0x02, 0x00, 0x0F, 0x00, 0x0C, 0x02, 0x30, 0x07, 0x80, 0x18, + 0x01, 0x80, 0x18, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, 0x06, 0x00, 0xC0, 0x18, + 0x03, 0x00, 0x7F, 0xEF, 0xFD, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x03, + 0xFF, 0xFF, 0xF0, 0x06, 0x01, 0x80, 0x60, 0x00, 0x0F, 0xFF, 0xFF, 0xF0, + 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x7F, 0xEF, 0xFD, 0x80, 0x30, 0x06, + 0x00, 0xC0, 0x18, 0x03, 0xFF, 0xFF, 0xF0, 0x0C, 0x03, 0xC0, 0xCC, 0x00, + 0x0F, 0xFF, 0xFF, 0xF0, 0x06, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x7F, 0xEF, + 0xFD, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, 0x03, 0xFF, 0xFF, 0xF0, 0x19, + 0x83, 0x30, 0x00, 0x7F, 0xFF, 0xFF, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x18, + 0x03, 0xFF, 0x7F, 0xEC, 0x01, 0x80, 0x30, 0x06, 0x00, 0xC0, 0x1F, 0xFF, + 0xFF, 0x80, 0x61, 0x86, 0x03, 0x18, 0xC6, 0x31, 0x8C, 0x63, 0x18, 0xC6, + 0x31, 0x8C, 0x60, 0x33, 0x30, 0x06, 0x31, 0x8C, 0x63, 0x18, 0xC6, 0x31, + 0x8C, 0x63, 0x18, 0xC0, 0x31, 0xEC, 0xC0, 0x30, 0xC3, 0x0C, 0x30, 0xC3, + 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0xCF, 0x30, 0x18, 0x61, 0x86, + 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x00, 0x7F, 0xC1, + 0xFF, 0xC6, 0x07, 0x18, 0x06, 0x60, 0x19, 0x80, 0x36, 0x00, 0xFF, 0x03, + 0xFC, 0x0D, 0x80, 0x36, 0x00, 0xD8, 0x07, 0x60, 0x19, 0x81, 0xE7, 0xFF, + 0x1F, 0xF0, 0x1E, 0x81, 0xF8, 0x31, 0x0E, 0x03, 0xE0, 0x3F, 0x03, 0xF0, + 0x3D, 0x83, 0xDC, 0x3C, 0xC3, 0xC6, 0x3C, 0x63, 0xC3, 0x3C, 0x33, 0xC1, + 0xBC, 0x0F, 0xC0, 0xFC, 0x07, 0xC0, 0x70, 0x06, 0x00, 0x06, 0x00, 0x06, + 0x00, 0x00, 0x00, 0x7C, 0x03, 0xFE, 0x0E, 0x0E, 0x38, 0x0E, 0x60, 0x0D, + 0x80, 0x0F, 0x00, 0x1E, 0x00, 0x3C, 0x00, 0x78, 0x00, 0xF0, 0x01, 0xB0, + 0x06, 0x70, 0x1C, 0x70, 0x70, 0x7F, 0xC0, 0x3E, 0x00, 0x00, 0xC0, 0x03, + 0x00, 0x0C, 0x00, 0x00, 0x00, 0x7C, 0x03, 0xFE, 0x0E, 0x0E, 0x38, 0x0E, + 0x60, 0x0D, 0x80, 0x0F, 0x00, 0x1E, 0x00, 0x3C, 0x00, 0x78, 0x00, 0xF0, + 0x01, 0xB0, 0x06, 0x70, 0x1C, 0x70, 0x70, 0x7F, 0xC0, 0x3E, 0x00, 0x03, + 0x00, 0x0F, 0x00, 0x33, 0x00, 0x00, 0x00, 0x7C, 0x03, 0xFE, 0x0E, 0x0E, + 0x38, 0x0E, 0x60, 0x0D, 0x80, 0x0F, 0x00, 0x1E, 0x00, 0x3C, 0x00, 0x78, + 0x00, 0xF0, 0x01, 0xB0, 0x06, 0x70, 0x1C, 0x70, 0x70, 0x7F, 0xC0, 0x3E, + 0x00, 0x06, 0x20, 0x1F, 0xC0, 0x37, 0x00, 0x00, 0x00, 0x7C, 0x03, 0xFE, + 0x0E, 0x0E, 0x38, 0x0E, 0x60, 0x0D, 0x80, 0x0F, 0x00, 0x1E, 0x00, 0x3C, + 0x00, 0x78, 0x00, 0xF0, 0x01, 0xB0, 0x06, 0x70, 0x1C, 0x70, 0x70, 0x7F, + 0xC0, 0x3E, 0x00, 0x06, 0x60, 0x0C, 0xC0, 0x00, 0x00, 0x3E, 0x01, 0xFF, + 0x07, 0x07, 0x1C, 0x07, 0x30, 0x06, 0xC0, 0x07, 0x80, 0x0F, 0x00, 0x1E, + 0x00, 0x3C, 0x00, 0x78, 0x00, 0xD8, 0x03, 0x38, 0x0E, 0x38, 0x38, 0x3F, + 0xE0, 0x1F, 0x00, 0xC1, 0xB1, 0x8D, 0x83, 0x81, 0xC1, 0xB1, 0x8D, 0x82, + 0x07, 0xC2, 0x3F, 0xE8, 0xE0, 0xE3, 0x80, 0xE6, 0x02, 0xD8, 0x0C, 0xF0, + 0x31, 0xE0, 0xC3, 0xC3, 0x07, 0x8C, 0x0F, 0x30, 0x1B, 0xC0, 0x67, 0x01, + 0xCF, 0x07, 0x37, 0xFC, 0x43, 0xE0, 0x0C, 0x00, 0x60, 0x03, 0x00, 0x00, + 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, + 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x37, 0x0E, 0x7F, 0xC1, 0xF8, + 0x03, 0x00, 0x60, 0x0C, 0x00, 0x00, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, + 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, + 0xC0, 0x37, 0x0E, 0x7F, 0xC1, 0xF8, 0x0C, 0x01, 0xE0, 0x33, 0x00, 0x00, + 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, + 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x37, 0x0E, 0x7F, 0xC1, 0xF8, + 0x19, 0x81, 0x98, 0x00, 0x0C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, + 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, 0xC0, 0x3C, 0x03, + 0x70, 0xE7, 0xFC, 0x1F, 0x80, 0x01, 0x80, 0x0C, 0x00, 0x60, 0x00, 0x00, + 0xE0, 0x1D, 0x80, 0x67, 0x03, 0x0C, 0x0C, 0x18, 0x60, 0x73, 0x80, 0xCC, + 0x01, 0xE0, 0x07, 0x80, 0x0C, 0x00, 0x30, 0x00, 0xC0, 0x03, 0x00, 0x0C, + 0x00, 0x30, 0x00, 0xC0, 0xC0, 0x18, 0x03, 0xFE, 0x7F, 0xEC, 0x0F, 0x80, + 0xF0, 0x1E, 0x03, 0xC0, 0x78, 0x1F, 0xFF, 0x7F, 0xCC, 0x01, 0x80, 0x30, + 0x06, 0x00, 0x3E, 0x1F, 0xCE, 0x3B, 0x06, 0xC1, 0xB0, 0xEC, 0xF3, 0x3E, + 0xC1, 0xB0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x6C, 0xFB, 0x38, 0x30, 0x04, + 0x00, 0x80, 0x00, 0x3E, 0x3F, 0xEC, 0x38, 0x06, 0x01, 0x83, 0xE7, 0xFB, + 0x86, 0xC1, 0xB0, 0xE7, 0xFD, 0xE3, 0x06, 0x03, 0x01, 0x80, 0x00, 0x3E, + 0x3F, 0xEC, 0x38, 0x06, 0x01, 0x83, 0xE7, 0xFB, 0x86, 0xC1, 0xB0, 0xE7, + 0xFD, 0xE3, 0x1C, 0x05, 0x03, 0x60, 0x00, 0x3E, 0x3F, 0xEC, 0x38, 0x06, + 0x01, 0x83, 0xE7, 0xFB, 0x86, 0xC1, 0xB0, 0xE7, 0xFD, 0xE3, 0x19, 0x0F, + 0xC2, 0x70, 0x00, 0x3E, 0x3F, 0xEC, 0x38, 0x06, 0x01, 0x83, 0xE7, 0xFB, + 0x86, 0xC1, 0xB0, 0xE7, 0xFD, 0xE3, 0x33, 0x0C, 0xC0, 0x00, 0xF8, 0xFF, + 0xB0, 0xE0, 0x18, 0x06, 0x0F, 0x9F, 0xEE, 0x1B, 0x06, 0xC3, 0x9F, 0xF7, + 0x8C, 0x0E, 0x07, 0xC3, 0xB0, 0xEC, 0x1F, 0x03, 0x83, 0xE3, 0xFE, 0xC3, + 0x80, 0x60, 0x18, 0x3E, 0x7F, 0xB8, 0x6C, 0x1B, 0x0E, 0x7F, 0xDE, 0x30, + 0x3E, 0x3E, 0x3F, 0xDF, 0xCC, 0x1E, 0x18, 0x07, 0x03, 0x01, 0x80, 0xCF, + 0xFF, 0xF7, 0xFF, 0xFF, 0x86, 0x00, 0xC1, 0x80, 0xF0, 0xF8, 0x6F, 0xE7, + 0xF9, 0xE0, 0xF8, 0x1E, 0x1F, 0xE6, 0x1B, 0x03, 0xC0, 0x30, 0x0C, 0x03, + 0x00, 0xC0, 0xD8, 0x77, 0xF8, 0x78, 0x08, 0x03, 0x00, 0x60, 0x98, 0x3C, + 0x00, 0x30, 0x06, 0x00, 0x80, 0x00, 0x1E, 0x0F, 0xE6, 0x1B, 0x03, 0xC0, + 0xFF, 0xFF, 0xFF, 0x00, 0xC0, 0xD8, 0x67, 0xF8, 0x78, 0x06, 0x03, 0x00, + 0x80, 0x00, 0x1E, 0x0F, 0xE6, 0x1B, 0x03, 0xC0, 0xFF, 0xFF, 0xFF, 0x00, + 0xC0, 0xD8, 0x67, 0xF8, 0x78, 0x1C, 0x05, 0x03, 0x60, 0x00, 0x1E, 0x0F, + 0xE6, 0x1B, 0x03, 0xC0, 0xFF, 0xFF, 0xFF, 0x00, 0xC0, 0xD8, 0x67, 0xF8, + 0x78, 0x33, 0x0C, 0xC0, 0x00, 0x78, 0x3F, 0x98, 0x6C, 0x0F, 0x03, 0xFF, + 0xFF, 0xFC, 0x03, 0x03, 0x61, 0x9F, 0xE1, 0xE0, 0xC6, 0x20, 0x33, 0x33, + 0x33, 0x33, 0x33, 0x33, 0x36, 0x40, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, + 0x30, 0xE4, 0xC0, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, 0x18, 0x61, 0x86, + 0xCF, 0x30, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x0C, 0x30, 0xC3, 0x00, + 0x00, 0x1D, 0x81, 0xC1, 0xF0, 0x06, 0x07, 0xC7, 0xF9, 0x86, 0xE1, 0xF0, + 0x3C, 0x0F, 0x03, 0xC0, 0xF8, 0x76, 0x19, 0xFE, 0x1E, 0x00, 0x11, 0x8F, + 0xC2, 0xF0, 0x00, 0xCF, 0x37, 0xFF, 0x1F, 0x83, 0xC0, 0xF0, 0x3C, 0x0F, + 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0x30, 0x06, 0x00, 0x80, 0x00, 0x1E, + 0x1F, 0xE6, 0x1B, 0x87, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xE1, 0xD8, 0x67, + 0xF8, 0x78, 0x06, 0x03, 0x01, 0x80, 0x00, 0x1E, 0x1F, 0xE6, 0x1B, 0x87, + 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xE1, 0xD8, 0x67, 0xF8, 0x78, 0x18, 0x0F, + 0x02, 0x60, 0x00, 0x1E, 0x1F, 0xE6, 0x1B, 0x87, 0xC0, 0xF0, 0x3C, 0x0F, + 0x03, 0xE1, 0xD8, 0x67, 0xF8, 0x78, 0x11, 0x0F, 0xC2, 0xF0, 0x00, 0x1E, + 0x1F, 0xE6, 0x1B, 0x87, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xE1, 0xD8, 0x67, + 0xF8, 0x78, 0x33, 0x0C, 0xC0, 0x00, 0x78, 0x7F, 0x98, 0x6E, 0x1F, 0x03, + 0xC0, 0xF0, 0x3C, 0x0F, 0x87, 0x61, 0x9F, 0xE1, 0xE0, 0x0C, 0x01, 0x80, + 0x00, 0x00, 0x0F, 0xFF, 0xFF, 0xC0, 0x00, 0x00, 0x0C, 0x01, 0x80, 0x1E, + 0x5F, 0xD6, 0x1B, 0x87, 0xC2, 0xF1, 0x3C, 0x8F, 0x43, 0xE1, 0xD8, 0x67, + 0xFA, 0x78, 0x00, 0x00, 0x30, 0x04, 0x00, 0x80, 0x00, 0xC0, 0xF0, 0x3C, + 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC1, 0xF8, 0xFF, 0xEC, 0xF3, + 0x06, 0x03, 0x00, 0x80, 0x00, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, + 0x3C, 0x0F, 0x03, 0xC1, 0xF8, 0xFF, 0xEC, 0xF3, 0x18, 0x0F, 0x02, 0x60, + 0x00, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x03, 0xC1, + 0xF8, 0xFF, 0xEC, 0xF3, 0x33, 0x0C, 0xC0, 0x03, 0x03, 0xC0, 0xF0, 0x3C, + 0x0F, 0x03, 0xC0, 0xF0, 0x3C, 0x0F, 0x07, 0xE3, 0xFF, 0xB3, 0xCC, 0x06, + 0x01, 0x80, 0xC0, 0x00, 0xC0, 0xD8, 0x36, 0x0D, 0x86, 0x31, 0x8C, 0x43, + 0x30, 0x6C, 0x1A, 0x07, 0x80, 0xE0, 0x30, 0x0C, 0x03, 0x01, 0x81, 0xE0, + 0x70, 0x00, 0xC0, 0x18, 0x03, 0x00, 0x67, 0x8F, 0xF9, 0xE3, 0xB8, 0x3E, + 0x03, 0xC0, 0x78, 0x0F, 0x01, 0xF0, 0x7F, 0x1D, 0xFF, 0x33, 0xC6, 0x00, + 0xC0, 0x18, 0x03, 0x00, 0x00, 0x19, 0x86, 0x60, 0x03, 0x03, 0x60, 0xD8, + 0x36, 0x18, 0xC6, 0x31, 0x0C, 0xC1, 0xB0, 0x68, 0x1E, 0x03, 0x80, 0xC0, + 0x30, 0x0C, 0x06, 0x07, 0x81, 0xC0 +}; + +const GFXglyph FreeSans11pt8bGlyphs[] PROGMEM = { + { 0, 0, 0, 6, 0, 1 }, // 0, 0x00 ' ' + { 0, 2, 16, 7, 3, -15 }, // 1, 0x01 '!' + { 4, 5, 5, 7, 1, -15 }, // 2, 0x02 '"' + { 8, 12, 15, 12, 0, -14 }, // 3, 0x03 '#' + { 31, 11, 18, 12, 1, -16 }, // 4, 0x04 '$' + { 56, 18, 16, 20, 1, -15 }, // 5, 0x05 '%' + { 92, 13, 16, 15, 1, -15 }, // 6, 0x06 '&' + { 118, 2, 5, 4, 1, -15 }, // 7, 0x07 ''' + { 120, 5, 21, 7, 2, -15 }, // 8, 0x08 '(' + { 134, 5, 21, 7, 1, -15 }, // 9, 0x09 ')' + { 148, 7, 6, 9, 1, -15 }, // 10, 0x0A '*' + { 154, 10, 10, 13, 2, -9 }, // 11, 0x0B '+' + { 167, 2, 5, 6, 2, -1 }, // 12, 0x0C ',' + { 169, 5, 2, 7, 1, -6 }, // 13, 0x0D '-' + { 171, 2, 2, 5, 2, -1 }, // 14, 0x0E '.' + { 172, 6, 16, 6, 0, -15 }, // 15, 0x0F '/' + { 184, 10, 16, 12, 1, -15 }, // 16, 0x10 '0' + { 204, 5, 16, 12, 3, -15 }, // 17, 0x11 '1' + { 214, 10, 16, 12, 1, -15 }, // 18, 0x12 '2' + { 234, 10, 16, 12, 1, -15 }, // 19, 0x13 '3' + { 254, 10, 16, 12, 1, -15 }, // 20, 0x14 '4' + { 274, 10, 16, 12, 1, -15 }, // 21, 0x15 '5' + { 294, 10, 16, 12, 1, -15 }, // 22, 0x16 '6' + { 314, 10, 16, 12, 1, -15 }, // 23, 0x17 '7' + { 334, 10, 16, 12, 1, -15 }, // 24, 0x18 '8' + { 354, 10, 16, 12, 1, -15 }, // 25, 0x19 '9' + { 374, 2, 12, 5, 2, -11 }, // 26, 0x1A ':' + { 377, 2, 14, 6, 2, -10 }, // 27, 0x1B ';' + { 381, 11, 11, 13, 1, -10 }, // 28, 0x1C '<' + { 397, 11, 6, 13, 1, -7 }, // 29, 0x1D '=' + { 406, 11, 11, 13, 1, -10 }, // 30, 0x1E '>' + { 422, 9, 16, 12, 2, -15 }, // 31, 0x1F '?' + { 440, 20, 19, 22, 1, -15 }, // 32, 0x20 '@' + { 488, 14, 16, 15, 0, -15 }, // 33, 0x21 'A' + { 516, 12, 16, 15, 2, -15 }, // 34, 0x22 'B' + { 540, 14, 16, 16, 1, -15 }, // 35, 0x23 'C' + { 568, 12, 16, 15, 2, -15 }, // 36, 0x24 'D' + { 592, 11, 16, 14, 2, -15 }, // 37, 0x25 'E' + { 614, 10, 16, 13, 2, -15 }, // 38, 0x26 'F' + { 634, 15, 16, 17, 1, -15 }, // 39, 0x27 'G' + { 664, 12, 16, 16, 2, -15 }, // 40, 0x28 'H' + { 688, 2, 16, 6, 2, -15 }, // 41, 0x29 'I' + { 692, 9, 16, 12, 1, -15 }, // 42, 0x2A 'J' + { 710, 13, 16, 15, 2, -15 }, // 43, 0x2B 'K' + { 736, 9, 16, 12, 2, -15 }, // 44, 0x2C 'L' + { 754, 15, 16, 19, 2, -15 }, // 45, 0x2D 'M' + { 784, 12, 16, 16, 2, -15 }, // 46, 0x2E 'N' + { 808, 15, 16, 17, 1, -15 }, // 47, 0x2F 'O' + { 838, 11, 16, 14, 2, -15 }, // 48, 0x30 'P' + { 860, 15, 17, 17, 1, -15 }, // 49, 0x31 'Q' + { 892, 13, 16, 16, 2, -15 }, // 50, 0x32 'R' + { 918, 12, 16, 15, 2, -15 }, // 51, 0x33 'S' + { 942, 12, 16, 14, 1, -15 }, // 52, 0x34 'T' + { 966, 12, 16, 16, 2, -15 }, // 53, 0x35 'U' + { 990, 14, 16, 14, 0, -15 }, // 54, 0x36 'V' + { 1018, 20, 16, 21, 0, -15 }, // 55, 0x37 'W' + { 1058, 14, 16, 14, 0, -15 }, // 56, 0x38 'X' + { 1086, 14, 16, 15, 1, -15 }, // 57, 0x39 'Y' + { 1114, 12, 16, 14, 1, -15 }, // 58, 0x3A 'Z' + { 1138, 4, 21, 6, 1, -15 }, // 59, 0x3B '[' + { 1149, 6, 16, 6, 0, -15 }, // 60, 0x3C '\' + { 1161, 4, 21, 6, 0, -15 }, // 61, 0x3D ']' + { 1172, 8, 8, 10, 1, -15 }, // 62, 0x3E '^' + { 1180, 13, 1, 12, 0, 4 }, // 63, 0x3F '_' + { 1182, 4, 3, 6, 0, -15 }, // 64, 0x40 '`' + { 1184, 10, 12, 12, 1, -11 }, // 65, 0x41 'a' + { 1199, 11, 16, 12, 1, -15 }, // 66, 0x42 'b' + { 1221, 10, 12, 11, 1, -11 }, // 67, 0x43 'c' + { 1236, 10, 16, 12, 1, -15 }, // 68, 0x44 'd' + { 1256, 10, 12, 12, 1, -11 }, // 69, 0x45 'e' + { 1271, 4, 16, 6, 1, -15 }, // 70, 0x46 'f' + { 1279, 10, 17, 12, 1, -11 }, // 71, 0x47 'g' + { 1301, 10, 16, 12, 1, -15 }, // 72, 0x48 'h' + { 1321, 2, 16, 5, 1, -15 }, // 73, 0x49 'i' + { 1325, 4, 21, 5, 0, -15 }, // 74, 0x4A 'j' + { 1336, 10, 16, 11, 1, -15 }, // 75, 0x4B 'k' + { 1356, 2, 16, 5, 1, -15 }, // 76, 0x4C 'l' + { 1360, 16, 12, 18, 1, -11 }, // 77, 0x4D 'm' + { 1384, 10, 12, 12, 1, -11 }, // 78, 0x4E 'n' + { 1399, 10, 12, 12, 1, -11 }, // 79, 0x4F 'o' + { 1414, 11, 16, 12, 1, -11 }, // 80, 0x50 'p' + { 1436, 10, 16, 12, 1, -11 }, // 81, 0x51 'q' + { 1456, 6, 12, 7, 1, -11 }, // 82, 0x52 'r' + { 1465, 9, 12, 11, 1, -11 }, // 83, 0x53 's' + { 1479, 4, 14, 6, 1, -13 }, // 84, 0x54 't' + { 1486, 10, 12, 12, 1, -11 }, // 85, 0x55 'u' + { 1501, 11, 12, 11, 0, -11 }, // 86, 0x56 'v' + { 1518, 16, 12, 16, 0, -11 }, // 87, 0x57 'w' + { 1542, 10, 12, 10, 0, -11 }, // 88, 0x58 'x' + { 1557, 10, 17, 11, 0, -11 }, // 89, 0x59 'y' + { 1579, 9, 12, 11, 1, -11 }, // 90, 0x5A 'z' + { 1593, 5, 21, 7, 1, -15 }, // 91, 0x5B '{' + { 1607, 2, 21, 6, 2, -15 }, // 92, 0x5C '|' + { 1613, 5, 21, 7, 2, -15 }, // 93, 0x5D '}' + { 1627, 9, 4, 11, 1, -9 }, // 94, 0x5E '~' + { 1632, 16, 21, 18, 1, -17 }, // 95, 0x5F ' DELETE' + { 1674, 0, 0, 6, 0, 1 }, // 96, 0x60 'NO-BREAK SPACE' + { 1674, 2, 16, 7, 2, -11 }, // 97, 0x61 'INVERTED EXCLAMATION MARK' + { 1678, 10, 14, 12, 1, -12 }, // 98, 0x62 'CENT SIGN' + { 1696, 11, 16, 12, 0, -15 }, // 99, 0x63 'POUND SIGN' + { 1718, 13, 16, 14, 0, -15 }, // 100, 0x64 'EURO SIGN *' + { 1744, 10, 16, 12, 1, -15 }, // 101, 0x65 'YEN SIGN' + { 1764, 12, 20, 15, 2, -19 }, // 102, 0x66 'LATIN CAPITAL LETTER S WITH CARON *' + { 1794, 10, 21, 12, 1, -15 }, // 103, 0x67 'SECTION SIGN' + { 1821, 9, 16, 11, 1, -15 }, // 104, 0x68 'LATIN SMALL LETTER S WITH CARON *' + { 1839, 16, 16, 18, 1, -15 }, // 105, 0x69 'COPYRIGHT SIGN' + { 1871, 6, 9, 8, 1, -15 }, // 106, 0x6A 'FEMININE ORDINAL INDICATOR' + { 1878, 7, 7, 11, 2, -8 }, // 107, 0x6B 'LEFT-POINTING DOUBLE ANGLE QUOTATION MARK' + { 1885, 11, 6, 13, 1, -8 }, // 108, 0x6C 'NOT SIGN' + { 1894, 5, 2, 7, 1, -6 }, // 109, 0x6D 'SOFT HYPHEN' + { 1896, 16, 16, 18, 1, -15 }, // 110, 0x6E 'REGISTERED SIGN' + { 1928, 6, 2, 7, 1, -15 }, // 111, 0x6F 'MACRON' + { 1930, 7, 8, 13, 3, -15 }, // 112, 0x70 'DEGREE SIGN' + { 1937, 11, 14, 13, 1, -12 }, // 113, 0x71 'PLUS-MINUS SIGN' + { 1957, 7, 9, 8, 1, -17 }, // 114, 0x72 'SUPERSCRIPT TWO' + { 1965, 7, 10, 8, 1, -17 }, // 115, 0x73 'SUPERSCRIPT THREE' + { 1974, 12, 20, 14, 1, -19 }, // 116, 0x74 'LATIN CAPITAL LETTER Z WITH CARON *' + { 2004, 11, 15, 12, 1, -11 }, // 117, 0x75 'MICRO SIGN' + { 2025, 10, 19, 12, 2, -15 }, // 118, 0x76 'PILCROW SIGN' + { 2049, 2, 2, 6, 2, -6 }, // 119, 0x77 'MIDDLE DOT' + { 2050, 9, 16, 11, 1, -15 }, // 120, 0x78 'LATIN SMALL LETTER Z WITH CARON *' + { 2068, 3, 9, 8, 3, -16 }, // 121, 0x79 'SUPERSCRIPT ONE' + { 2072, 6, 9, 8, 1, -15 }, // 122, 0x7A 'MASCULINE ORDINAL INDICATOR' + { 2079, 7, 7, 11, 2, -9 }, // 123, 0x7B 'RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK' + { 2086, 19, 16, 22, 1, -15 }, // 124, 0x7C 'LATIN CAPITAL LIGATURE OE *' + { 2124, 19, 12, 20, 1, -11 }, // 125, 0x7D 'LATIN SMALL LIGATURE OE *' + { 2153, 14, 19, 15, 1, -18 }, // 126, 0x7E 'LATIN CAPITAL LETTER Y WITH DIAERESIS *' + { 2187, 9, 16, 12, 2, -11 }, // 127, 0x7F 'INVERTED QUESTION MARK' + { 2205, 14, 20, 15, 0, -19 }, // 128, 0x80 'LATIN CAPITAL LETTER A WITH GRAVE' + { 2240, 14, 20, 15, 0, -19 }, // 129, 0x81 'LATIN CAPITAL LETTER A WITH ACUTE' + { 2275, 14, 20, 15, 0, -19 }, // 130, 0x82 'LATIN CAPITAL LETTER A WITH CIRCUMFLEX' + { 2310, 14, 20, 15, 0, -19 }, // 131, 0x83 'LATIN CAPITAL LETTER A WITH TILDE' + { 2345, 14, 19, 15, 0, -18 }, // 132, 0x84 'LATIN CAPITAL LETTER A WITH DIAERESIS' + { 2379, 14, 22, 15, 0, -21 }, // 133, 0x85 'LATIN CAPITAL LETTER A WITH RING ABOVE' + { 2418, 20, 16, 22, 0, -15 }, // 134, 0x86 'LATIN CAPITAL LETTER AE' + { 2458, 14, 21, 16, 1, -15 }, // 135, 0x87 'LATIN CAPITAL LETTER C WITH CEDILLA' + { 2495, 11, 20, 14, 2, -19 }, // 136, 0x88 'LATIN CAPITAL LETTER E WITH GRAVE' + { 2523, 11, 20, 14, 2, -19 }, // 137, 0x89 'LATIN CAPITAL LETTER E WITH ACUTE' + { 2551, 11, 20, 14, 2, -19 }, // 138, 0x8A 'LATIN CAPITAL LETTER E WITH CIRCUMFLEX' + { 2579, 11, 19, 14, 2, -18 }, // 139, 0x8B 'LATIN CAPITAL LETTER E WITH DIAERESIS' + { 2606, 5, 20, 6, 0, -19 }, // 140, 0x8C 'LATIN CAPITAL LETTER I WITH GRAVE' + { 2619, 5, 20, 6, 1, -19 }, // 141, 0x8D 'LATIN CAPITAL LETTER I WITH ACUTE' + { 2632, 6, 20, 7, 0, -19 }, // 142, 0x8E 'LATIN CAPITAL LETTER I WITH CIRCUMFLEX' + { 2647, 6, 19, 7, 1, -18 }, // 143, 0x8F 'LATIN CAPITAL LETTER I WITH DIAERESIS' + { 2662, 14, 16, 16, 1, -15 }, // 144, 0x90 'LATIN CAPITAL LETTER ETH' + { 2690, 12, 19, 16, 2, -18 }, // 145, 0x91 'LATIN CAPITAL LETTER N WITH TILDE' + { 2719, 15, 20, 17, 1, -19 }, // 146, 0x92 'LATIN CAPITAL LETTER O WITH GRAVE' + { 2757, 15, 20, 17, 1, -19 }, // 147, 0x93 'LATIN CAPITAL LETTER O WITH ACUTE' + { 2795, 15, 20, 17, 1, -19 }, // 148, 0x94 'LATIN CAPITAL LETTER O WITH CIRCUMFLEX' + { 2833, 15, 20, 17, 1, -19 }, // 149, 0x95 'LATIN CAPITAL LETTER O WITH TILDE' + { 2871, 15, 19, 17, 1, -18 }, // 150, 0x96 'LATIN CAPITAL LETTER O WITH DIAERESIS' + { 2907, 9, 8, 13, 2, -8 }, // 151, 0x97 'MULTIPLICATION SIGN' + { 2916, 15, 16, 17, 1, -15 }, // 152, 0x98 'LATIN CAPITAL LETTER O WITH STROKE' + { 2946, 12, 20, 16, 2, -19 }, // 153, 0x99 'LATIN CAPITAL LETTER U WITH GRAVE' + { 2976, 12, 20, 16, 2, -19 }, // 154, 0x9A 'LATIN CAPITAL LETTER U WITH ACUTE' + { 3006, 12, 20, 16, 2, -19 }, // 155, 0x9B 'LATIN CAPITAL LETTER U WITH CIRCUMFLEX' + { 3036, 12, 19, 16, 2, -18 }, // 156, 0x9C 'LATIN CAPITAL LETTER U WITH DIAERESIS' + { 3065, 14, 20, 15, 1, -19 }, // 157, 0x9D 'LATIN CAPITAL LETTER Y WITH ACUTE' + { 3100, 11, 16, 14, 2, -15 }, // 158, 0x9E 'LATIN CAPITAL LETTER THORN' + { 3122, 10, 16, 13, 2, -15 }, // 159, 0x9F 'LATIN SMALL LETTER SHARP S' + { 3142, 10, 16, 12, 1, -15 }, // 160, 0xA0 'LATIN SMALL LETTER A WITH GRAVE' + { 3162, 10, 16, 12, 1, -15 }, // 161, 0xA1 'LATIN SMALL LETTER A WITH ACUTE' + { 3182, 10, 16, 12, 1, -15 }, // 162, 0xA2 'LATIN SMALL LETTER A WITH CIRCUMFLEX' + { 3202, 10, 16, 12, 1, -15 }, // 163, 0xA3 'LATIN SMALL LETTER A WITH TILDE' + { 3222, 10, 15, 12, 1, -14 }, // 164, 0xA4 'LATIN SMALL LETTER A WITH DIAERESIS' + { 3241, 10, 18, 12, 1, -17 }, // 165, 0xA5 'LATIN SMALL LETTER A WITH RING ABOVE' + { 3264, 18, 12, 19, 1, -11 }, // 166, 0xA6 'LATIN SMALL LETTER AE' + { 3291, 10, 17, 11, 1, -11 }, // 167, 0xA7 'LATIN SMALL LETTER C WITH CEDILLA' + { 3313, 10, 16, 12, 1, -15 }, // 168, 0xA8 'LATIN SMALL LETTER E WITH GRAVE' + { 3333, 10, 16, 12, 1, -15 }, // 169, 0xA9 'LATIN SMALL LETTER E WITH ACUTE' + { 3353, 10, 16, 12, 1, -15 }, // 170, 0xAA 'LATIN SMALL LETTER E WITH CIRCUMFLEX' + { 3373, 10, 15, 12, 1, -14 }, // 171, 0xAB 'LATIN SMALL LETTER E WITH DIAERESIS' + { 3392, 4, 16, 5, 0, -15 }, // 172, 0xAC 'LATIN SMALL LETTER I WITH GRAVE' + { 3400, 4, 16, 5, 0, -15 }, // 173, 0xAD 'LATIN SMALL LETTER I WITH ACUTE' + { 3408, 6, 16, 6, -1, -15 }, // 174, 0xAE 'LATIN SMALL LETTER I WITH CIRCUMFLEX' + { 3420, 6, 15, 6, 0, -14 }, // 175, 0xAF 'LATIN SMALL LETTER I WITH DIAERESIS' + { 3432, 10, 17, 12, 1, -16 }, // 176, 0xB0 'LATIN SMALL LETTER ETH' + { 3454, 10, 16, 12, 1, -15 }, // 177, 0xB1 'LATIN SMALL LETTER N WITH TILDE' + { 3474, 10, 16, 12, 1, -15 }, // 178, 0xB2 'LATIN SMALL LETTER O WITH GRAVE' + { 3494, 10, 16, 12, 1, -15 }, // 179, 0xB3 'LATIN SMALL LETTER O WITH ACUTE' + { 3514, 10, 16, 12, 1, -15 }, // 180, 0xB4 'LATIN SMALL LETTER O WITH CIRCUMFLEX' + { 3534, 10, 16, 12, 1, -15 }, // 181, 0xB5 'LATIN SMALL LETTER O WITH TILDE' + { 3554, 10, 15, 12, 1, -14 }, // 182, 0xB6 'LATIN SMALL LETTER O WITH DIAERESIS' + { 3573, 11, 10, 13, 1, -9 }, // 183, 0xB7 'DIVISION SIGN' + { 3587, 10, 13, 12, 1, -11 }, // 184, 0xB8 'LATIN SMALL LETTER O WITH STROKE' + { 3604, 10, 16, 12, 1, -15 }, // 185, 0xB9 'LATIN SMALL LETTER U WITH GRAVE' + { 3624, 10, 16, 12, 1, -15 }, // 186, 0xBA 'LATIN SMALL LETTER U WITH ACUTE' + { 3644, 10, 16, 12, 1, -15 }, // 187, 0xBB 'LATIN SMALL LETTER U WITH CIRCUMFLEX' + { 3664, 10, 15, 12, 1, -14 }, // 188, 0xBC 'LATIN SMALL LETTER U WITH DIAERESIS' + { 3683, 10, 21, 11, 0, -15 }, // 189, 0xBD 'LATIN SMALL LETTER Y WITH ACUTE' + { 3710, 11, 19, 12, 1, -14 }, // 190, 0xBE 'LATIN SMALL LETTER THORN' + { 3737, 10, 20, 11, 0, -14 } +}; // 191, 0xBF 'LATIN SMALL LETTER Y WITH DIAERESIS' + +const GFXfont FreeSans11pt8b PROGMEM = { + (uint8_t*)FreeSans11pt8bBitmaps, + (GFXglyph*)FreeSans11pt8bGlyphs, + 0x20, 0xDF, 26 +}; + +// Approx. 5113 bytes diff --git a/include/fonts/Picopixel.h b/include/fonts/Picopixel.h new file mode 100644 index 000000000..acc5425ef --- /dev/null +++ b/include/fonts/Picopixel.h @@ -0,0 +1,123 @@ +// Picopixel by Sebastian Weber. A tiny font +// with all characters within a 6 pixel height. + +const uint8_t PicopixelBitmaps[] PROGMEM = { + 0xE8, 0xB4, 0x57, 0xD5, 0xF5, 0x00, 0x4E, 0x3E, 0x80, 0xA5, 0x4A, 0x4A, + 0x5A, 0x50, 0xC0, 0x6A, 0x40, 0x95, 0x80, 0xAA, 0x80, 0x5D, 0x00, 0x60, + 0xE0, 0x80, 0x25, 0x48, 0x56, 0xD4, 0x75, 0x40, 0xC5, 0x4E, 0xC5, 0x1C, + 0x97, 0x92, 0xF3, 0x1C, 0x53, 0x54, 0xE5, 0x48, 0x55, 0x54, 0x55, 0x94, + 0xA0, 0x46, 0x64, 0xE3, 0x80, 0x98, 0xC5, 0x04, 0x56, 0xC6, 0x57, 0xDA, + 0xD7, 0x5C, 0x72, 0x46, 0xD6, 0xDC, 0xF3, 0xCE, 0xF3, 0x48, 0x72, 0xD4, + 0xB7, 0xDA, 0xF8, 0x24, 0xD4, 0xBB, 0x5A, 0x92, 0x4E, 0x8E, 0xEB, 0x58, + 0x80, 0x9D, 0xB9, 0x90, 0x56, 0xD4, 0xD7, 0x48, 0x56, 0xD4, 0x40, 0xD7, + 0x5A, 0x71, 0x1C, 0xE9, 0x24, 0xB6, 0xD4, 0xB6, 0xA4, 0x8C, 0x6B, 0x55, + 0x00, 0xB5, 0x5A, 0xB5, 0x24, 0xE5, 0x4E, 0xEA, 0xC0, 0x91, 0x12, 0xD5, + 0xC0, 0x54, 0xF0, 0x90, 0xC7, 0xF0, 0x93, 0x5E, 0x71, 0x80, 0x25, 0xDE, + 0x5E, 0x30, 0x6E, 0x80, 0x77, 0x9C, 0x93, 0x5A, 0xB8, 0x45, 0x60, 0x92, + 0xEA, 0xAA, 0x40, 0xD5, 0x6A, 0xD6, 0x80, 0x55, 0x00, 0xD7, 0x40, 0x75, + 0x90, 0xE8, 0x71, 0xE0, 0xBA, 0x40, 0xB5, 0x80, 0xB5, 0x00, 0x8D, 0x54, + 0xAA, 0x80, 0xAC, 0xE0, 0xE5, 0x70, 0x6A, 0x26, 0xFC, 0xC8, 0xAC, 0x5A +}; + +const GFXglyph PicopixelGlyphs[] PROGMEM = {{0, 0, 0, 2, 0, 1}, // 0x20 ' ' + {0, 1, 5, 2, 0, -4}, // 0x21 '!' + {1, 3, 2, 4, 0, -4}, // 0x22 '"' + {2, 5, 5, 6, 0, -4}, // 0x23 '#' + {6, 3, 6, 4, 0, -4}, // 0x24 '$' + {9, 3, 5, 4, 0, -4}, // 0x25 '%' + {11, 4, 5, 5, 0, -4}, // 0x26 '&' + {14, 1, 2, 2, 0, -4}, // 0x27 ''' + {15, 2, 5, 3, 0, -4}, // 0x28 '(' + {17, 2, 5, 3, 0, -4}, // 0x29 ')' + {19, 3, 3, 4, 0, -3}, // 0x2A '*' + {21, 3, 3, 4, 0, -3}, // 0x2B '+' + {23, 2, 2, 3, 0, 0}, // 0x2C ',' + {24, 3, 1, 4, 0, -2}, // 0x2D '-' + {25, 1, 1, 2, 0, 0}, // 0x2E '.' + {26, 3, 5, 4, 0, -4}, // 0x2F '/' + {28, 3, 5, 4, 0, -4}, // 0x30 '0' + {30, 2, 5, 3, 0, -4}, // 0x31 '1' + {32, 3, 5, 4, 0, -4}, // 0x32 '2' + {34, 3, 5, 4, 0, -4}, // 0x33 '3' + {36, 3, 5, 4, 0, -4}, // 0x34 '4' + {38, 3, 5, 4, 0, -4}, // 0x35 '5' + {40, 3, 5, 4, 0, -4}, // 0x36 '6' + {42, 3, 5, 4, 0, -4}, // 0x37 '7' + {44, 3, 5, 4, 0, -4}, // 0x38 '8' + {46, 3, 5, 4, 0, -4}, // 0x39 '9' + {48, 1, 3, 2, 0, -3}, // 0x3A ':' + {49, 2, 4, 3, 0, -3}, // 0x3B ';' + {50, 2, 3, 3, 0, -3}, // 0x3C '<' + {51, 3, 3, 4, 0, -3}, // 0x3D '=' + {53, 2, 3, 3, 0, -3}, // 0x3E '>' + {54, 3, 5, 4, 0, -4}, // 0x3F '?' + {56, 3, 5, 4, 0, -4}, // 0x40 '@' + {58, 3, 5, 4, 0, -4}, // 0x41 'A' + {60, 3, 5, 4, 0, -4}, // 0x42 'B' + {62, 3, 5, 4, 0, -4}, // 0x43 'C' + {64, 3, 5, 4, 0, -4}, // 0x44 'D' + {66, 3, 5, 4, 0, -4}, // 0x45 'E' + {68, 3, 5, 4, 0, -4}, // 0x46 'F' + {70, 3, 5, 4, 0, -4}, // 0x47 'G' + {72, 3, 5, 4, 0, -4}, // 0x48 'H' + {74, 1, 5, 2, 0, -4}, // 0x49 'I' + {75, 3, 5, 4, 0, -4}, // 0x4A 'J' + {77, 3, 5, 4, 0, -4}, // 0x4B 'K' + {79, 3, 5, 4, 0, -4}, // 0x4C 'L' + {81, 5, 5, 6, 0, -4}, // 0x4D 'M' + {85, 4, 5, 5, 0, -4}, // 0x4E 'N' + {88, 3, 5, 4, 0, -4}, // 0x4F 'O' + {90, 3, 5, 4, 0, -4}, // 0x50 'P' + {92, 3, 6, 4, 0, -4}, // 0x51 'Q' + {95, 3, 5, 4, 0, -4}, // 0x52 'R' + {97, 3, 5, 4, 0, -4}, // 0x53 'S' + {99, 3, 5, 4, 0, -4}, // 0x54 'T' + {101, 3, 5, 4, 0, -4}, // 0x55 'U' + {103, 3, 5, 4, 0, -4}, // 0x56 'V' + {105, 5, 5, 6, 0, -4}, // 0x57 'W' + {109, 3, 5, 4, 0, -4}, // 0x58 'X' + {111, 3, 5, 4, 0, -4}, // 0x59 'Y' + {113, 3, 5, 4, 0, -4}, // 0x5A 'Z' + {115, 2, 5, 3, 0, -4}, // 0x5B '[' + {117, 3, 5, 4, 0, -4}, // 0x5C '\' + {119, 2, 5, 3, 0, -4}, // 0x5D ']' + {121, 3, 2, 4, 0, -4}, // 0x5E '^' + {122, 4, 1, 4, 0, 1}, // 0x5F '_' + {123, 2, 2, 3, 0, -4}, // 0x60 '`' + {124, 3, 4, 4, 0, -3}, // 0x61 'a' + {126, 3, 5, 4, 0, -4}, // 0x62 'b' + {128, 3, 3, 4, 0, -2}, // 0x63 'c' + {130, 3, 5, 4, 0, -4}, // 0x64 'd' + {132, 3, 4, 4, 0, -3}, // 0x65 'e' + {134, 2, 5, 3, 0, -4}, // 0x66 'f' + {136, 3, 5, 4, 0, -3}, // 0x67 'g' + {138, 3, 5, 4, 0, -4}, // 0x68 'h' + {140, 1, 5, 2, 0, -4}, // 0x69 'i' + {141, 2, 6, 3, 0, -4}, // 0x6A 'j' + {143, 3, 5, 4, 0, -4}, // 0x6B 'k' + {145, 2, 5, 3, 0, -4}, // 0x6C 'l' + {147, 5, 3, 6, 0, -2}, // 0x6D 'm' + {149, 3, 3, 4, 0, -2}, // 0x6E 'n' + {151, 3, 3, 4, 0, -2}, // 0x6F 'o' + {153, 3, 4, 4, 0, -2}, // 0x70 'p' + {155, 3, 4, 4, 0, -2}, // 0x71 'q' + {157, 2, 3, 3, 0, -2}, // 0x72 'r' + {158, 3, 4, 4, 0, -3}, // 0x73 's' + {160, 2, 5, 3, 0, -4}, // 0x74 't' + {162, 3, 3, 4, 0, -2}, // 0x75 'u' + {164, 3, 3, 4, 0, -2}, // 0x76 'v' + {166, 5, 3, 6, 0, -2}, // 0x77 'w' + {168, 3, 3, 4, 0, -2}, // 0x78 'x' + {170, 3, 4, 4, 0, -2}, // 0x79 'y' + {172, 3, 4, 4, 0, -3}, // 0x7A 'z' + {174, 3, 5, 4, 0, -4}, // 0x7B '{' + {176, 1, 6, 2, 0, -4}, // 0x7C '|' + {177, 3, 5, 4, 0, -4}, // 0x7D '}' + {179, 4, 2, 5, 0, -3} +}; // 0x7E '~' + +const GFXfont Picopixel PROGMEM = {(uint8_t*)PicopixelBitmaps, + (GFXglyph*)PicopixelGlyphs, 0x20, 0x7E, 7 + }; + +// Approx. 852 bytes diff --git a/include/fonts/ows_font_CEI_8859-15.cpp b/include/fonts/ows_font_CEI_8859-15.cpp new file mode 100644 index 000000000..7aac7e412 --- /dev/null +++ b/include/fonts/ows_font_CEI_8859-15.cpp @@ -0,0 +1,269 @@ + +#undef PROGMEM +#define PROGMEM +/** + * @brief Default font used by the Open Smart Wacth + * + * This font is a 5x7 pixel size using the ISO CEI 8859-15 ASCII table + * + * https://fr.wikipedia.org/wiki/ISO/CEI_8859-15 + * + */ +static const unsigned char OSWfont[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x3E, 0x5B, 0x4F, 0x5B, 0x3E, + 0x3E, 0x6B, 0x4F, 0x6B, 0x3E, + 0x1C, 0x3E, 0x7C, 0x3E, 0x1C, + 0x18, 0x3C, 0x7E, 0x3C, 0x18, + 0x1C, 0x57, 0x7D, 0x57, 0x1C, + 0x1C, 0x5E, 0x7F, 0x5E, 0x1C, + 0x00, 0x18, 0x3C, 0x18, 0x00, + 0xFF, 0xE7, 0xC3, 0xE7, 0xFF, + 0x00, 0x18, 0x24, 0x18, 0x00, + 0xFF, 0xE7, 0xDB, 0xE7, 0xFF, + 0x30, 0x48, 0x3A, 0x06, 0x0E, + 0x26, 0x29, 0x79, 0x29, 0x26, + 0x40, 0x7F, 0x05, 0x05, 0x07, + 0x40, 0x7F, 0x05, 0x25, 0x3F, + 0x5A, 0x3C, 0xE7, 0x3C, 0x5A, + 0x7F, 0x3E, 0x1C, 0x1C, 0x08, + 0x08, 0x1C, 0x1C, 0x3E, 0x7F, + 0x14, 0x22, 0x7F, 0x22, 0x14, + 0x5F, 0x5F, 0x00, 0x5F, 0x5F, + 0x06, 0x09, 0x7F, 0x01, 0x7F, + 0x00, 0x66, 0x89, 0x95, 0x6A, + 0x60, 0x60, 0x60, 0x60, 0x60, + 0x94, 0xA2, 0xFF, 0xA2, 0x94, + 0x08, 0x04, 0x7E, 0x04, 0x08, + 0x10, 0x20, 0x7E, 0x20, 0x10, + 0x08, 0x08, 0x2A, 0x1C, 0x08, + 0x08, 0x1C, 0x2A, 0x08, 0x08, + 0x1E, 0x10, 0x10, 0x10, 0x10, + 0x0C, 0x1E, 0x0C, 0x1E, 0x0C, + 0x30, 0x38, 0x3E, 0x38, 0x30, + 0x06, 0x0E, 0x3E, 0x0E, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x5F, 0x00, 0x00, + 0x00, 0x07, 0x00, 0x07, 0x00, + 0x14, 0x7F, 0x14, 0x7F, 0x14, + 0x24, 0x2A, 0x7F, 0x2A, 0x12, + 0x23, 0x13, 0x08, 0x64, 0x62, + 0x36, 0x49, 0x56, 0x20, 0x50, + 0x00, 0x08, 0x07, 0x03, 0x00, + 0x00, 0x1C, 0x22, 0x41, 0x00, + 0x00, 0x41, 0x22, 0x1C, 0x00, + 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, + 0x08, 0x08, 0x3E, 0x08, 0x08, + 0x00, 0x80, 0x70, 0x30, 0x00, + 0x08, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x00, 0x60, 0x60, 0x00, + 0x20, 0x10, 0x08, 0x04, 0x02, + 0x3E, 0x51, 0x49, 0x45, 0x3E, + 0x00, 0x42, 0x7F, 0x40, 0x00, //50 + 0x72, 0x49, 0x49, 0x49, 0x46, + 0x21, 0x41, 0x49, 0x4D, 0x33, + 0x18, 0x14, 0x12, 0x7F, 0x10, + 0x27, 0x45, 0x45, 0x45, 0x39, + 0x3C, 0x4A, 0x49, 0x49, 0x31, + 0x41, 0x21, 0x11, 0x09, 0x07, + 0x36, 0x49, 0x49, 0x49, 0x36, + 0x46, 0x49, 0x49, 0x29, 0x1E, + 0x00, 0x00, 0x14, 0x00, 0x00, + 0x00, 0x40, 0x34, 0x00, 0x00, + 0x00, 0x08, 0x14, 0x22, 0x41, //60 + 0x14, 0x14, 0x14, 0x14, 0x14, + 0x00, 0x41, 0x22, 0x14, 0x08, + 0x02, 0x01, 0x59, 0x09, 0x06, + 0x3E, 0x41, 0x5D, 0x59, 0x4E, + 0x7C, 0x12, 0x11, 0x12, 0x7C, //65 | x41 -> A + 0x7F, 0x49, 0x49, 0x49, 0x36, + 0x3E, 0x41, 0x41, 0x41, 0x22, + 0x7F, 0x41, 0x41, 0x41, 0x3E, + 0x7F, 0x49, 0x49, 0x49, 0x41, + 0x7F, 0x09, 0x09, 0x09, 0x01, //70 + 0x3E, 0x41, 0x41, 0x51, 0x73, + 0x7F, 0x08, 0x08, 0x08, 0x7F, + 0x00, 0x41, 0x7F, 0x41, 0x00, + 0x20, 0x40, 0x41, 0x3F, 0x01, + 0x7F, 0x08, 0x14, 0x22, 0x41, + 0x7F, 0x40, 0x40, 0x40, 0x40, + 0x7F, 0x02, 0x1C, 0x02, 0x7F, + 0x7F, 0x04, 0x08, 0x10, 0x7F, + 0x3E, 0x41, 0x41, 0x41, 0x3E, + 0x7F, 0x09, 0x09, 0x09, 0x06, //80 + 0x3E, 0x41, 0x51, 0x21, 0x5E, + 0x7F, 0x09, 0x19, 0x29, 0x46, + 0x26, 0x49, 0x49, 0x49, 0x32, + 0x03, 0x01, 0x7F, 0x01, 0x03, + 0x3F, 0x40, 0x40, 0x40, 0x3F, + 0x1F, 0x20, 0x40, 0x20, 0x1F, + 0x3F, 0x40, 0x38, 0x40, 0x3F, + 0x63, 0x14, 0x08, 0x14, 0x63, + 0x03, 0x04, 0x78, 0x04, 0x03, + 0x61, 0x59, 0x49, 0x4D, 0x43, //90 + 0x00, 0x7F, 0x41, 0x41, 0x41, + 0x02, 0x04, 0x08, 0x10, 0x20, + 0x00, 0x41, 0x41, 0x41, 0x7F, + 0x04, 0x02, 0x01, 0x02, 0x04, + 0x40, 0x40, 0x40, 0x40, 0x40, + 0x00, 0x03, 0x07, 0x08, 0x00, + 0x20, 0x54, 0x54, 0x78, 0x40, //97 | x61 -> a + 0x7F, 0x28, 0x44, 0x44, 0x38, //98 -> b + 0x38, 0x44, 0x44, 0x44, 0x28, //99 -> c + 0x38, 0x44, 0x44, 0x28, 0x7F, //100 -> d + 0x38, 0x54, 0x54, 0x54, 0x18, //101 -> e + 0x00, 0x08, 0x7E, 0x09, 0x02, //102 -> f + 0x18, 0xA4, 0xA4, 0x9C, 0x78, //103 + 0x7F, 0x08, 0x04, 0x04, 0x78, //104 + 0x00, 0x44, 0x7D, 0x40, 0x00, //105 + 0x20, 0x40, 0x40, 0x3D, 0x00, //106 + 0x7F, 0x10, 0x28, 0x44, 0x00, //107 + 0x00, 0x41, 0x7F, 0x40, 0x00, //108 + 0x7C, 0x04, 0x78, 0x04, 0x78, //109 + 0x7C, 0x08, 0x04, 0x04, 0x78, //110 + 0x38, 0x44, 0x44, 0x44, 0x38, //111 + 0xFC, 0x18, 0x24, 0x24, 0x18, //112 + 0x18, 0x24, 0x24, 0x18, 0xFC, //113 + 0x7C, 0x08, 0x04, 0x04, 0x08, //114 + 0x48, 0x54, 0x54, 0x54, 0x24, //115 + 0x04, 0x04, 0x3F, 0x44, 0x24, //116 + 0x3C, 0x40, 0x40, 0x20, 0x7C, //117 + 0x1C, 0x20, 0x40, 0x20, 0x1C, //118 + 0x3C, 0x40, 0x30, 0x40, 0x3C, //119 + 0x44, 0x28, 0x10, 0x28, 0x44, //120 + 0x4C, 0x90, 0x90, 0x90, 0x7C, //121 + 0x44, 0x64, 0x54, 0x4C, 0x44, //122 + 0x00, 0x08, 0x36, 0x41, 0x00, //123 + 0x00, 0x00, 0x77, 0x00, 0x00, //124 + 0x00, 0x41, 0x36, 0x08, 0x00, //125 + 0x02, 0x01, 0x02, 0x04, 0x02, //126 + 0x3C, 0x26, 0x23, 0x26, 0x3C, //127 + 0x1E, 0xA1, 0xA1, 0x61, 0x12, //128 + 0x3A, 0x40, 0x40, 0x20, 0x7A, //129 + 0x38, 0x54, 0x54, 0x55, 0x59, //130 + 0x21, 0x55, 0x55, 0x79, 0x41, //131 + 0x22, 0x54, 0x54, 0x78, 0x42, //132 + 0x21, 0x55, 0x54, 0x78, 0x40, + 0x20, 0x54, 0x55, 0x79, 0x40, + 0x0C, 0x1E, 0x52, 0x72, 0x12, + 0x39, 0x55, 0x55, 0x55, 0x59, + 0x39, 0x54, 0x54, 0x54, 0x59, + 0x39, 0x55, 0x54, 0x54, 0x58, + 0x00, 0x00, 0x45, 0x7C, 0x41, + 0x00, 0x02, 0x45, 0x7D, 0x42, //140 + 0x00, 0x01, 0x45, 0x7C, 0x40, + 0x7D, 0x12, 0x11, 0x12, 0x7D, // + 0xF0, 0x28, 0x25, 0x28, 0xF0, + 0x7C, 0x54, 0x55, 0x45, 0x00, + 0x20, 0x54, 0x54, 0x7C, 0x54, + 0x7C, 0x0A, 0x09, 0x7F, 0x49, + 0x32, 0x49, 0x49, 0x49, 0x32, + 0x3A, 0x44, 0x44, 0x44, 0x3A, // o-umlaut + 0x32, 0x4A, 0x48, 0x48, 0x30, + 0x3A, 0x41, 0x41, 0x21, 0x7A, //150 + 0x3A, 0x42, 0x40, 0x20, 0x78, + 0x00, 0x9D, 0xA0, 0xA0, 0x7D, + 0x3D, 0x42, 0x42, 0x42, 0x3D, // O-umlaut + 0x3D, 0x40, 0x40, 0x40, 0x3D, + 0x3C, 0x24, 0xFF, 0x24, 0x24, + 0x48, 0x7E, 0x49, 0x43, 0x66, + 0x2B, 0x2F, 0xFC, 0x2F, 0x2B, + 0xFF, 0x09, 0x29, 0xF6, 0x20, + 0xC0, 0x88, 0x7E, 0x09, 0x03, + 0x20, 0x54, 0x54, 0x79, 0x41, //160 + 0x00, 0x00, 0x44, 0x7D, 0x41, + 0x30, 0x48, 0x48, 0x4A, 0x32, + 0x38, 0x40, 0x40, 0x22, 0x7A, + 0x14, 0x3E, 0x55, 0x41, 0x22, //164 | A4 -> € + 0x7D, 0x0D, 0x19, 0x31, 0x7D, + 0x26, 0x29, 0x29, 0x2F, 0x28, + 0x26, 0x29, 0x29, 0x29, 0x26, + 0x30, 0x48, 0x4D, 0x40, 0x20, + 0x38, 0x08, 0x08, 0x08, 0x08, + 0x08, 0x08, 0x08, 0x08, 0x38, //170 + 0x2F, 0x10, 0xC8, 0xAC, 0xBA, + 0x2F, 0x10, 0x28, 0x34, 0xFA, + 0x00, 0x00, 0x7B, 0x00, 0x00, + 0x08, 0x14, 0x2A, 0x14, 0x22, + 0x22, 0x14, 0x2A, 0x14, 0x08, + 0x55, 0x00, 0x55, 0x00, 0x55, // 176 + 0xAA, 0x55, 0xAA, 0x55, 0xAA, // + 0xFF, 0x55, 0xFF, 0x55, 0xFF, // + 0x00, 0x00, 0x00, 0xFF, 0x00, + 0x10, 0x10, 0x10, 0xFF, 0x00, //180 + 0x14, 0x14, 0x14, 0xFF, 0x00, + 0x10, 0x10, 0xFF, 0x00, 0xFF, + 0x10, 0x10, 0xF0, 0x10, 0xF0, + 0x14, 0x14, 0x14, 0xFC, 0x00, + 0x14, 0x14, 0xF7, 0x00, 0xFF, + 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x14, 0x14, 0xF4, 0x04, 0xFC, + 0x14, 0x14, 0x17, 0x10, 0x1F, + 0x10, 0x10, 0x1F, 0x10, 0x1F, + 0x14, 0x14, 0x14, 0x1F, 0x00, //190 + 0x10, 0x10, 0x10, 0xF0, 0x00, + 0x00, 0x00, 0x00, 0x1F, 0x10, + 0x10, 0x10, 0x10, 0x1F, 0x10, + 0x10, 0x10, 0x10, 0xF0, 0x10, + 0x00, 0x00, 0x00, 0xFF, 0x10, + 0x78, 0x15, 0x12, 0x15, 0x78, //196 | C4 -> Ä + 0x10, 0x10, 0x10, 0xFF, 0x10, + 0x00, 0x00, 0x00, 0xFF, 0x14, + 0x00, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x00, 0x1F, 0x10, 0x17, //200 + 0x00, 0x00, 0xFC, 0x04, 0xF4, + 0x14, 0x14, 0x17, 0x10, 0x17, + 0x14, 0x14, 0xF4, 0x04, 0xF4, + 0x00, 0x00, 0xFF, 0x00, 0xF7, + 0x14, 0x14, 0x14, 0x14, 0x14, + 0x14, 0x14, 0xF7, 0x00, 0xF7, + 0x14, 0x14, 0x14, 0x17, 0x14, + 0x10, 0x10, 0x1F, 0x10, 0x1F, + 0x14, 0x14, 0x14, 0xF4, 0x14, + 0x10, 0x10, 0xF0, 0x10, 0xF0, //210 + 0x00, 0x00, 0x1F, 0x10, 0x1F, + 0x00, 0x00, 0x00, 0x1F, 0x14, + 0x00, 0x00, 0x00, 0xFC, 0x14, + 0x00, 0x00, 0xF0, 0x10, 0xF0, + 0x10, 0x10, 0xFF, 0x10, 0xFF, + 0x14, 0x14, 0x14, 0xFF, 0x14, + 0x10, 0x10, 0x10, 0x1F, 0x00, + 0x00, 0x00, 0x00, 0xF0, 0x10, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xF0, 0xF0, 0xF0, 0xF0, 0xF0, //220 + 0xFF, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFF, 0xFF, + 0x0F, 0x0F, 0x0F, 0x0F, 0x0F, + 0x7E, 0x01, 0x49, 0x56, 0x20, // DF -> ß + 0x20, 0x55, 0x56, 0x7C, 0x40, // 225 | E0 -> à + 0x7E, 0x02, 0x02, 0x06, 0x06, // 226 | E1 + 0x02, 0x7E, 0x02, 0x7E, 0x02, // 227 | E2 + 0x63, 0x55, 0x49, 0x41, 0x63, // 228 | E3 + 0x38, 0x44, 0x44, 0x3C, 0x04, // 229 | E4 + 0x40, 0x7E, 0x20, 0x1E, 0x20, // 230 | E5 + 0x06, 0x02, 0x7E, 0x02, 0x02, // 231 | E6 + 0x0E, 0x51, 0x71, 0x11, 0x0A, // 231 | E7 -> ç + 0x38, 0x55, 0x56, 0x54, 0x18, // E8 -> è + 0x38, 0x56, 0x55, 0x54, 0x18, // E9 -> é + 0x30, 0x4A, 0x4D, 0x4D, 0x30, // EA -> ê + 0x30, 0x48, 0x78, 0x48, 0x30, // EB -> ë + 0xBC, 0x62, 0x5A, 0x46, 0x3D, // EC -> ì + 0x3E, 0x49, 0x49, 0x49, 0x00, // 238 | ED + 0x7E, 0x01, 0x01, 0x01, 0x7E, // 239 | EF + 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, // 240 | F0 + 0x44, 0x44, 0x5F, 0x44, 0x44, + 0x40, 0x51, 0x4A, 0x44, 0x40, + 0x40, 0x44, 0x4A, 0x51, 0x40, + 0x00, 0x00, 0xFF, 0x01, 0x03, + 0xE0, 0x80, 0xFF, 0x00, 0x00, + 0x08, 0x08, 0x6B, 0x6B, 0x08, + 0x36, 0x12, 0x36, 0x24, 0x36, + 0x06, 0x0F, 0x09, 0x0F, 0x06, + 0x00, 0x00, 0x18, 0x18, 0x00, + 0x00, 0x00, 0x10, 0x10, 0x00, // 250 + 0x30, 0x40, 0xFF, 0x01, 0x01, + 0x00, 0x1F, 0x01, 0x01, 0x1E, + 0x00, 0x19, 0x1D, 0x17, 0x12, + 0x00, 0x3C, 0x3C, 0x3C, 0x3C, + 0x00, 0x00, 0x00, 0x00, 0x00 // #255 NBSP +}; \ No newline at end of file diff --git a/include/gfx_2d.h b/include/gfx_2d.h new file mode 100644 index 000000000..5ecf42244 --- /dev/null +++ b/include/gfx_2d.h @@ -0,0 +1,1202 @@ +#ifndef P3DT_GFX_2D_H +#define P3DT_GFX_2D_H + +#include + +#include "gfx_util.h" +#include "math_angles.h" + +enum CIRC_OPT { DRAW_UPPER_RIGHT, DRAW_UPPER_LEFT, DRAW_LOWER_RIGHT, DRAW_LOWER_LEFT, DRAW_ALL }; + +class DrawPixel { + public: + DrawPixel() {} + virtual void drawPixel(int32_t x, int32_t y, uint16_t color) {}; +}; + +class Graphics2D { + public: + Graphics2D(uint16_t w_, uint16_t h_, uint8_t chunkHeight_, bool isRound_ = false, bool allocatePsram_ = false) + : width(w_), height(h_), chunkHeight(chunkHeight_), isRound(isRound_), allocatePsram(allocatePsram_) { + enableBuffer(); + maskEnabled = false; + maskColor = rgb565(0, 0, 0); + alphaEnabled = false; + } + + void enableBuffer() { + drawPixelCallback = NULL; + uint16_t numChunks = height / chunkHeight; + buffer = new uint16_t* [numChunks]; + if (isRound) { + missingPixelColor = rgb565(128, 128, 128); + chunkXOffsets = new uint16_t[numChunks]; + chunkWidths = new uint16_t[numChunks]; + for (uint16_t i = 0; i < numChunks; i++) { + uint16_t y = i * chunkHeight; + float y1 = (y + (y < height / 2 ? chunkHeight : 0)) - height / 2.0; + float d = sqrt(120 * 120 - y1 * y1); + + uint16_t xOffset = 120 - d; + uint16_t chunkWidth = ceil(d * 2); + + chunkXOffsets[i] = xOffset; + chunkWidths[i] = chunkWidth; + // Serial.print("Chunk: "); + // Serial.println(i); + // Serial.print(" Width: "); + // Serial.println(chunkWidth); + // Serial.print(" chunkHeight: "); + // Serial.println(chunkHeight); + // Serial.print(" Size: "); + // Serial.println(chunkWidth * chunkHeight); + // buffer[i] = new uint16_t[chunkWidth * chunkHeight]; + if (allocatePsram) { +#if defined(GPS_EDITION) || defined(GPS_EDITION_ROTATED) + buffer[i] = (uint16_t*)ps_malloc(width * chunkHeight * sizeof(uint16_t)); +#else + buffer[i] = new uint16_t[width * chunkHeight](); +#endif + } else { + buffer[i] = new uint16_t[width * chunkHeight](); + } + } + } else { + for (uint16_t i = 0; i < numChunks; i++) { + // buffer[i] = new uint16_t[width * chunkHeight]; + // (uint8_t*) malloc( BufferSize * sizeof(uint8_t) ) + if (allocatePsram) { +#if defined(GPS_EDITION) || defined(GPS_EDITION_ROTATED) + buffer[i] = (uint16_t*)ps_malloc(width * chunkHeight * sizeof(uint16_t)); +#else + buffer[i] = (uint16_t*)malloc(width * chunkHeight * sizeof(uint16_t)); +#endif + } else { + buffer[i] = (uint16_t*)malloc(width * chunkHeight * sizeof(uint16_t)); + } + } + } + } + + bool hasBuffer() { + return drawPixelCallback == NULL; + } + void disableBuffer(DrawPixel* callback) { // + delay(1000); + drawPixelCallback = callback; + uint16_t numChunks = height / chunkHeight; + for (uint16_t i = 0; i < numChunks; i++) { + delete[] buffer[i]; + buffer[i] = NULL; + } + delete[] buffer; + buffer = NULL; + + // delete[] chunkXOffsets; + // chunkXOffsets = NULL; + + // delete[] chunkWidths; + // chunkWidths = NULL; + } + + ~Graphics2D() { + uint16_t numChunks = height / chunkHeight; + for (uint16_t i = 0; i < numChunks; i++) { + delete[] buffer[i]; + buffer[i] = NULL; + } + delete[] buffer; + buffer = NULL; + + // delete[] chunkXOffsets; + // chunkXOffsets = NULL; + + // delete[] chunkWidths; + // chunkWidths = NULL; + } + + uint16_t numChunks() { + return height / chunkHeight; + } + uint16_t* getChunk(uint8_t chunkId) { + return buffer[chunkId]; + } + uint8_t getChunkHeight() { + return chunkHeight; + } + uint16_t getChunkOffset(uint8_t chunkId) { + return isRound ? chunkXOffsets[chunkId] : 0; + } + uint16_t getChunkWidth(uint8_t chunkId) { + return isRound ? chunkWidths[chunkId] : width; + } + uint16_t getHeight() { + return height; + } + uint16_t getWidth() { + return width; + } + + bool isMaskEnabled() { + return maskEnabled; + } + void enableMask(uint16_t color) { + maskEnabled = true; + maskColor = color; + } + void disableMask() { + maskColor = false; + } + void enableAlpha(float a) { + alpha = a; + alphaEnabled = true; + } + void disableAplha() { + alphaEnabled = false; + } + + void setMissingPixelColor(uint16_t color) { + missingPixelColor = color; + } + uint16_t getMissingPixelColor(void) { + return missingPixelColor; + } + + // no other functions should be allowed to access the buffer in write mode due to the chunk mapping + + /** + * @brief Draw a pixel of color 'color' a x-y position. + * + * @param x x axis coordinate + * @param y y axis coordinate + * @param color color code of the pixel + */ + void drawPixel(int32_t x, int32_t y, uint16_t color) { + drawPixelClipped(x, y, color); + } + + void drawPixelClipped(int32_t x, int32_t y, uint16_t color) { + if (x >= width || y >= height || x < 0 || y < 0) { + return; + } + if (maskEnabled && color == maskColor) { + return; + } + + // if we have a pixel callback, there is now buffer + // draw with the callback and return.. + if (drawPixelCallback != NULL) { + drawPixelCallback->drawPixel(x, y, color); + return; + } + + uint8_t chunkId = y / chunkHeight; + int16_t chunkY = y - chunkId * chunkHeight; + + // + if (alphaEnabled) { + if (isRound && isInsideChunk(x, y)) { + int16_t chunkX = x - chunkXOffsets[chunkId]; + color = blend(buffer[chunkId][chunkX + chunkY * chunkWidths[chunkId]], color, alpha); + } else { + color = blend(buffer[chunkId][x + chunkY * width], color, alpha); + } + } + + if (isRound && isInsideChunk(x, y)) { + int16_t chunkX = x - chunkXOffsets[chunkId]; + buffer[chunkId][chunkX + chunkY * chunkWidths[chunkId]] = color; + } else if (!isRound) { // fix for round module + buffer[chunkId][x + chunkY * width] = color; + } + } + + uint16_t getPixel(uint16_t x, uint16_t y) { + if (x >= width || y >= height) { + return 0; + } + uint8_t chunkId = y / chunkHeight; + uint16_t chunkY = y - chunkId * chunkHeight; + // printf("chunkid %d, offetY %d for y=%d and chunkHeight=%d\n", chunkId, chunkY, y, chunkHeight); + if (isRound) { + // TODO: check if inside chunk + if (isInsideChunk(x, y)) { + uint16_t chunkX = x - chunkXOffsets[chunkId]; + return buffer[chunkId][chunkX + chunkY * chunkWidths[chunkId]]; + } else { + return missingPixelColor; + } + } else { + return buffer[chunkId][x + chunkY * width]; + } + } + + bool isInsideChunk(uint16_t x, uint16_t y) { + uint8_t chunkId = y / chunkHeight; + // uint16_t chunkY = y - chunkId * chunkHeight; + uint16_t chunkOffset = chunkXOffsets[chunkId]; + uint16_t chunkWidth = chunkWidths[chunkId]; + bool xFit = chunkOffset < x && x < chunkOffset + chunkWidth; + // y always fits, because we chunk in rows + return xFit; + } + + /** + * @brief Draw an horizontal line from the point (x,y) to an other horizontal point at h pixels + * + * @param x x-axis of the start point + * @param y y-axis of the start point + * @param w width of the horizontal line + * @param color color code of the line + */ + void drawHLine(int32_t x, int32_t y, uint16_t w, uint16_t color) { + for (uint16_t i = 0; i < w; i++) { + drawPixel(x + i, y, color); + } + } + + /** + * @brief Draw a vertical line from the bottom point (x,y) to an other vertical point at h pixels + * + * @param x x-axis of the start point + * @param y y-axis of the start point + * @param h height of the vertical line + * @param color color code of the line + */ + void drawVLine(int32_t x, int32_t y, uint16_t h, uint16_t color) { + for (uint16_t i = 0; i < h; i++) { + drawPixel(x, y + i, color); + } + } + + void drawFrame(int32_t x, int32_t y, uint16_t w, uint16_t h, uint16_t color) { + drawHLine(x, y, w, color); + drawHLine(x, y + h, w, color); + drawVLine(x, y, h, color); + drawVLine(x + w, y, h, color); + } + + void fillFrame(int32_t x0, int32_t y0, uint16_t w, uint16_t h, uint16_t color) { + for (uint16_t y = y0; y < y0 + h; y++) { + drawHLine(x0, y, w, color); + } + } + + /** + * Draw line from (x1,y1) to (x2,y2) point with color + * + * @param x1 + * @param y1 + * @param x2 + * @param y2 + * @param color + */ + void drawLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint16_t color) { // see p3dt_gfx_2d_license.txt + // printf("\ndrawLine(%d, %d, %d, %d)",x1,y1,x2,y2); + // see p3dt_gfx_2d_license.txt + int32_t tmp; + int32_t x, y; + int32_t dx, dy; + int32_t err; + int32_t ystep; + + uint8_t swapxy = 0; + + /* no intersection check at the moment, should be added... */ + + if (x1 > x2) + dx = x1 - x2; + else + dx = x2 - x1; + if (y1 > y2) + dy = y1 - y2; + else + dy = y2 - y1; + + if (dy > dx) { + swapxy = 1; + tmp = dx; + dx = dy; + dy = tmp; + tmp = x1; + x1 = y1; + y1 = tmp; + tmp = x2; + x2 = y2; + y2 = tmp; + } + if (x1 > x2) { + tmp = x1; + x1 = x2; + x2 = tmp; + tmp = y1; + y1 = y2; + y2 = tmp; + } + err = dx >> 1; + if (y2 > y1) + ystep = 1; + else + ystep = -1; + y = y1; + + if (x2 == 0xffff) x2--; + + for (x = x1; x <= x2; x++) { + if (swapxy == 0) + drawPixel(x, y, color); + else + drawPixel(y, x, color); + err -= abs(dy); + if (err < 0) { + y += ystep; + err += dx; + } + } + } + + /** + * @brief Draw a line between (x1,y1) and (x2,y2) with a thick of radius and with specific color + * + * Radius is a multiple of 4 pixels. + * + * @param x1 x-axis of the start point + * @param y1 y-axis of the start point + * @param x2 x-axis of the end point + * @param y2 y-axis of the end point + * @param radius radius of the line. Example : radius = 1 give a line of 4 px of diameter, radius 2 -> 8px, etc.... + * @param color color code use to draw the line. + * @param highQuality + */ + void drawThickLine(int32_t x1, int32_t y1, int32_t x2, int32_t y2, uint8_t radius, uint16_t color, + bool highQuality = false) { // see p3dt_gfx_2d_license.txt + + // see p3dt_gfx_2d_license.txt + int32_t tmp; + int32_t x, y; + int32_t dx, dy; + int32_t err; + int32_t ystep; + + uint8_t swapxy = 0; + + /* no intersection check at the moment, should be added... */ + + if (x1 > x2) + dx = x1 - x2; + else + dx = x2 - x1; + if (y1 > y2) + dy = y1 - y2; + else + dy = y2 - y1; + + if (dy > dx) { + swapxy = 1; + tmp = dx; + dx = dy; + dy = tmp; + tmp = x1; + x1 = y1; + y1 = tmp; + tmp = x2; + x2 = y2; + y2 = tmp; + } + if (x1 > x2) { + tmp = x1; + x1 = x2; + x2 = tmp; + tmp = y1; + y1 = y2; + y2 = tmp; + } + err = dx >> 1; + if (y2 > y1) + ystep = 1; + else + ystep = -1; + y = y1; + + if (x2 == 0xffff) x2--; + + for (x = x1; x <= x2; x++) { + if (swapxy == 0) { + if (highQuality) { + fillCircle(x, y, radius, color); + } else { + drawCircle(x, y, radius, color); + if (radius > 2) { + drawCircle(x, y, radius - 1, color); + } + if (radius > 3) { + drawCircle(x, y, radius - 2, color); + } + } + } else { + if (highQuality) { + fillCircle(y, x, radius, color); + } else { + drawCircle(y, x, radius, color); + if (radius > 2) { + drawCircle(y, x, radius - 1, color); + } + if (radius > 3) { + drawCircle(y, x, radius - 2, color); + } + } + } + + err -= (uint8_t)dy; + if (err < 0) { + y += (uint16_t)ystep; + err += (uint16_t)dx; + } + } + } + + void drawTriangle(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) { + drawLine(x0, y0, x1, y1, color); + drawLine(x1, y1, x2, y2, color); + drawLine(x2, y2, x0, y0, color); + } + + /* + * "Complex" Stuff: + */ + + void _drawCircleSection(uint16_t x, uint16_t y, uint16_t x0, uint16_t y0, uint16_t color, + CIRC_OPT option) { // see p3dt_gfx_2d_license.txt + + if (option == DRAW_UPPER_RIGHT || option == DRAW_ALL) { + drawPixel(x0 + x, y0 - y, color); + drawPixel(x0 + y, y0 - x, color); + } + + if (option == DRAW_UPPER_LEFT || option == DRAW_ALL) { + drawPixel(x0 - x, y0 - y, color); + drawPixel(x0 - y, y0 - x, color); + } + + if (option == DRAW_LOWER_RIGHT || option == DRAW_ALL) { + drawPixel(x0 + x, y0 + y, color); + drawPixel(x0 + y, y0 + x, color); + } + + if (option == DRAW_LOWER_LEFT || option == DRAW_ALL) { + drawPixel(x0 - x, y0 + y, color); + drawPixel(x0 - y, y0 + x, color); + } + } + + /** + * @brief Draw a circle + * + * @param x0 x-axis of the center of the circle + * @param y0 y-axis of the center of the circle + * @param rad radius of the circle + * @param color color code of the circle + * @param option + */ + void drawCircle(uint16_t x0, uint16_t y0, uint16_t rad, uint16_t color, + CIRC_OPT option = DRAW_ALL) { // see p3dt_gfx_2d_license.txt + + float f; + float ddFx; + float ddFy; + float x; + float y; + + f = 1; + f -= rad; + ddFx = 1; + ddFy = 0; + ddFy -= rad; + ddFy *= 2; + x = 0; + y = rad; + + _drawCircleSection(x, y, x0, y0, color, option); + + while (x < y) { + if (f >= 0) { + y--; + ddFy += 2; + f += ddFy; + } + x++; + ddFx += 2; + f += ddFx; + + _drawCircleSection(x, y, x0, y0, color, option); + } + } + + void _fillCircleSection(uint16_t x, uint16_t y, uint16_t x0, uint16_t y0, uint16_t color, + CIRC_OPT option) { // see p3dt_gfx_2d_license.txt + + if (option == DRAW_UPPER_RIGHT || option == DRAW_ALL) { + drawVLine(x0 + x, y0 - y, y + 1, color); + drawVLine(x0 + y, y0 - x, x + 1, color); + } + + if (option == DRAW_UPPER_LEFT || option == DRAW_ALL) { + drawVLine(x0 - x, y0 - y, y + 1, color); + drawVLine(x0 - y, y0 - x, x + 1, color); + } + + if (option == DRAW_LOWER_RIGHT || option == DRAW_ALL) { + drawVLine(x0 + x, y0, y + 1, color); + drawVLine(x0 + y, y0, x + 1, color); + } + + if (option == DRAW_LOWER_LEFT || option == DRAW_ALL) { + drawVLine(x0 - x, y0, y + 1, color); + drawVLine(x0 - y, y0, x + 1, color); + } + } + + void fillCircle(uint16_t x0, uint16_t y0, uint16_t rad, uint16_t color, + CIRC_OPT option = DRAW_ALL) { // see p3dt_gfx_2d_license.txt + + float f; + float ddFx; + float ddFy; + float x; + float y; + + f = 1; + f -= rad; + ddFx = 1; + ddFy = 0; + ddFy -= rad; + ddFy *= 2; + x = 0; + y = rad; + + _fillCircleSection(x, y, x0, y0, color, option); + + while (x < y) { + if (f >= 0) { + y--; + ddFy += 2; + f += ddFy; + } + x++; + ddFx += 2; + f += ddFx; + + _fillCircleSection(x, y, x0, y0, color, option); + } + } + + void _drawEllipseSection(uint16_t x, uint16_t y, uint16_t x0, uint16_t y0, uint16_t color, + CIRC_OPT option = DRAW_ALL) { // see p3dt_gfx_2d_license.txt + + /* upper right */ + if (option == DRAW_UPPER_RIGHT || option == DRAW_ALL) { + drawPixel(x0 + x, y0 - y, color); + } + + /* upper left */ + if (option == DRAW_UPPER_LEFT || option == DRAW_ALL) { + drawPixel(x0 - x, y0 - y, color); + } + + /* lower right */ + if (option == DRAW_LOWER_RIGHT || option == DRAW_ALL) { + drawPixel(x0 + x, y0 + y, color); + } + + /* lower left */ + if (option == DRAW_LOWER_LEFT || option == DRAW_ALL) { + drawPixel(x0 - x, y0 + y, color); + } + } + + void drawEllipse(uint16_t x0, uint16_t y0, uint16_t rx, uint16_t ry, uint16_t color, + CIRC_OPT option = DRAW_ALL) { // see p3dt_gfx_2d_license.txt + + float x; + float y; + float xchg; + float ychg; + float err; + float rxrx2; + float ryry2; + float stopx; + float stopy; + + rxrx2 = rx; + rxrx2 *= rx; + rxrx2 *= 2; + + ryry2 = ry; + ryry2 *= ry; + ryry2 *= 2; + + x = rx; + y = 0; + + xchg = 1; + xchg -= rx; + xchg -= rx; + xchg *= ry; + xchg *= ry; + + ychg = rx; + ychg *= rx; + + err = 0; + + stopx = ryry2; + stopx *= rx; + stopy = 0; + + while (stopx >= stopy) { + _drawEllipseSection(x, y, x0, y0, color, option); + y++; + stopy += rxrx2; + err += ychg; + ychg += rxrx2; + if (2 * err + xchg > 0) { + x--; + stopx -= ryry2; + err += xchg; + xchg += ryry2; + } + } + + x = 0; + y = ry; + + xchg = ry; + xchg *= ry; + + ychg = 1; + ychg -= ry; + ychg -= ry; + ychg *= rx; + ychg *= rx; + + err = 0; + + stopx = 0; + + stopy = rxrx2; + stopy *= ry; + + while (stopx <= stopy) { + _drawEllipseSection(x, y, x0, y0, color, option); + x++; + stopx += ryry2; + err += xchg; + xchg += ryry2; + if (2 * err + ychg > 0) { + y--; + stopy -= rxrx2; + err += ychg; + ychg += rxrx2; + } + } + } + + void _fillEllipseSection(uint16_t x, uint16_t y, uint16_t x0, uint16_t y0, uint16_t color, + CIRC_OPT option = DRAW_ALL) { // see p3dt_gfx_2d_license.txt + + /* upper right */ + if (option == DRAW_UPPER_RIGHT || option == DRAW_ALL) { + drawVLine(x0 + x, y0 - y, y + 1, color); + } + + /* upper left */ + if (option == DRAW_UPPER_LEFT || option == DRAW_ALL) { + drawVLine(x0 - x, y0 - y, y + 1, color); + } + + /* lower right */ + if (option == DRAW_LOWER_RIGHT || option == DRAW_ALL) { + drawVLine(x0 + x, y0, y + 1, color); + } + + /* lower left */ + if (option == DRAW_LOWER_LEFT || option == DRAW_ALL) { + drawVLine(x0 - x, y0, y + 1, color); + } + } + + void fillEllipse(uint16_t x0, uint16_t y0, uint16_t rx, uint16_t ry, uint16_t color, + CIRC_OPT option = DRAW_ALL) { // see p3dt_gfx_2d_license.txt + + float x; + float y; + float xchg; + float ychg; + float err; + float rxrx2; + float ryry2; + float stopx; + float stopy; + + rxrx2 = rx; + rxrx2 *= rx; + rxrx2 *= 2; + + ryry2 = ry; + ryry2 *= ry; + ryry2 *= 2; + + x = rx; + y = 0; + + xchg = 1; + xchg -= rx; + xchg -= rx; + xchg *= ry; + xchg *= ry; + + ychg = rx; + ychg *= rx; + + err = 0; + + stopx = ryry2; + stopx *= rx; + stopy = 0; + + while (stopx >= stopy) { + _fillEllipseSection(x, y, x0, y0, color, option); + y++; + stopy += rxrx2; + err += ychg; + ychg += rxrx2; + if (2 * err + xchg > 0) { + x--; + stopx -= ryry2; + err += xchg; + xchg += ryry2; + } + } + + x = 0; + y = ry; + + xchg = ry; + xchg *= ry; + + ychg = 1; + ychg -= ry; + ychg -= ry; + ychg *= rx; + ychg *= rx; + + err = 0; + + stopx = 0; + + stopy = rxrx2; + stopy *= ry; + + while (stopx <= stopy) { + _fillEllipseSection(x, y, x0, y0, color, option); + x++; + stopx += ryry2; + err += xchg; + xchg += ryry2; + if (2 * err + ychg > 0) { + y--; + stopy -= rxrx2; + err += ychg; + ychg += rxrx2; + } + } + } + + void drawRFrame(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t r, + uint16_t color) { // see p3dt_gfx_2d_license.txt + + uint16_t xl; + uint16_t yu; + + xl = x; + xl += r; + yu = y; + yu += r; + + { + uint16_t yl; + uint16_t xr; + + xr = x; + xr += w; + xr -= r; + xr -= 1; + + yl = y; + yl += h; + yl -= r; + yl -= 1; + + drawCircle(xl, yu, r, color, DRAW_UPPER_LEFT); + drawCircle(xr, yu, r, color, DRAW_UPPER_RIGHT); + drawCircle(xl, yl, r, color, DRAW_LOWER_LEFT); + drawCircle(xr, yl, r, color, DRAW_LOWER_RIGHT); + } + + { + uint16_t ww; + uint16_t hh; + + ww = w; + ww -= r; + ww -= r; + hh = h; + hh -= r; + hh -= r; + + xl++; + yu++; + + if (ww >= 3) { + ww -= 2; + h--; + drawHLine(xl, y, ww, color); + drawHLine(xl, y + h, ww, color); + } + + if (hh >= 3) { + hh -= 2; + w--; + drawVLine(x, yu, hh, color); + drawVLine(x + w, yu, hh, color); + } + } + } + + void fillRFrame(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t r, + uint16_t color) { // see p3dt_gfx_2d_license.txt + if(h < 2 * r) + //Prevent infinite looping + return; + + uint16_t xl; + uint16_t yu; + uint16_t yl; + uint16_t xr; + + xl = x; + xl += r; + yu = y; + yu += r; + + xr = x; + xr += w; + xr -= r; + xr -= 1; + + yl = y; + yl += h; + yl -= r; + yl -= 1; + + fillCircle(xl, yu, r, color, DRAW_UPPER_LEFT); + fillCircle(xr, yu, r, color, DRAW_UPPER_RIGHT); + fillCircle(xl, yl, r, color, DRAW_LOWER_LEFT); + fillCircle(xr, yl, r, color, DRAW_LOWER_RIGHT); + + { + uint16_t ww; + uint16_t hh; + + ww = w; + ww -= r; + ww -= r; + xl++; + yu++; + + if (ww >= 3) { + ww -= 2; + fillFrame(xl, y, ww, r + 1, color); + fillFrame(xl, yl, ww, r + 1, color); + } + + hh = h; + hh -= r; + hh -= r; + // h--; + if (hh >= 3) { + hh -= 2; + fillFrame(x, yu, w, hh, color); + } + } + } + + void drawTick(uint8_t cx, uint8_t cy, uint8_t r1, uint8_t r2, float angle, uint16_t color) { + drawLine(rpx(cx, r1, angle), rpy(cy, r1, angle), rpx(cx, r2, angle), rpy(cy, r2, angle), color); + } + + void drawThickTick(uint8_t cx, uint8_t cy, uint8_t r1, uint8_t r2, float angle, uint8_t radius, uint16_t color, + bool highQuality = false) { + drawThickLine(rpx(cx, r1, angle), rpy(cy, r1, angle), rpx(cx, r2, angle), rpy(cy, r2, angle), radius, color, + highQuality); + } + + /** + * @brief Draw N ticks around the clock to visualize the hours. + * + * @param cx center x axis + * @param cy center y axis + * @param r1 radius from the begin of the tick. + * @param r2 radius from the end of the tick. + * @param nTicks number of ticks to draw + * @param color color code + */ + void drawNTicks(uint8_t cx, uint8_t cy, uint8_t r1, uint8_t r2, uint8_t nTicks, uint16_t color) { + const float deltaAngle = 360.0 / nTicks; + for (uint16_t h = 0; h < nTicks; h++) { + drawTick(cx, cy, r1, r2, h * deltaAngle, color); + } + } + + /** + * @brief Draw 12 ticks around the clock to visualize the hours. + * + * @param cx center x axis + * @param cy center y axis + * @param r1 radius from the begin of the tick. + * @param r2 radius from the end of the tick. + * @param color color code + */ + void drawHourTicks(uint8_t cx, uint8_t cy, uint8_t r1, uint8_t r2, uint16_t color) { + drawNTicks(cx, cy, r1, r2, 12, color); + } + + /** + * @brief Draw the ticks around the clock to visualize the minutes. + * + * 60 ticks will be drawn around the clock. + * + * @param cx center x axis + * @param cy center y axis + * @param r1 rayon + * @param r2 + * @param color color code + */ + void drawMinuteTicks(uint8_t cx, uint8_t cy, uint8_t r1, uint8_t r2, uint16_t color) { + drawNTicks(cx, cy, r1, r2, 60, color); + } + + void drawArc(int16_t x, int16_t y, int16_t r1, int16_t r2, float start, float end, uint16_t color) { + this->drawArc(x, y, start, end, 1, r1, r2-r1, color); // This _should_ be the equivalent call... + } + + /** + * Draw an arc + * + * @param cx Arc X center coordinates + * @param cy Arc Y center coordinates + * @param start Beginning angle of the arc (in deg). O° is equivalent to 12AM + * @param stop End angle of the arc (in deg). + * @param steps Number of lines that will compose the arc. + * @param radius Radius of the arc from the cx/cy center + * @param lineRadius Radius of the line. Example : radius = 1 give a line of 4 px of diameter, radius 2 -> 8px, + * etc.... + * @param color Color code of the arc + * @param highQuality + */ + void drawArc(int32_t cx, int32_t cy, float start, float stop, uint16_t steps, uint16_t radius, uint8_t lineRadius, + uint16_t color, bool highQuality = false) { + int32_t x1 = rpx(cx, radius, start); + int32_t y1 = rpy(cy, radius, start); + // printf("\ndraw from %f,%f in %d steps", start, stop, steps); + + float arcLength = stop - start; + + for (uint16_t i = 1; i <= steps; i++) { + float segmentLength = i * (arcLength / steps); + // printf("\n rpx(%d, %d, %f + %f)", cx, radius, start, segmentLength); + + int32_t x2 = rpx(cx, radius, start + segmentLength); + int32_t y2 = rpy(cy, radius, start + segmentLength); + // printf("\n gfx2d.drawLine(%d, %d, %d, %d, color);", x1, y1, x2, y2); + drawThickLine(x1, y1, x2, y2, lineRadius, color, highQuality); + x1 = x2; + y1 = y2; + } + } + + void drawBWBitmap(uint16_t x0, uint16_t y0, uint16_t cnt, uint16_t h, uint8_t* bitmap, uint16_t color, + uint16_t bgColor = 0, bool drawBackground = false) { + // cnt: Number of bytes of the bitmap in horizontal direction. The width of the bitmap is cnt*8. + // h: Height of the bitmap. + + for (uint16_t x = 0; x < cnt; x++) { + for (uint16_t y = 0; y < h; y++) { + uint8_t bits = bitmap[x + y * cnt]; + for (uint8_t b = 1; b <= 8; b++) { + if (bits & (1 << (8 - b))) { + drawPixel(x * 8 + x0 + b, y + y0, color); + } else if (drawBackground) { + drawPixel(x * 8 + x0 + b, y + y0, bgColor); + } + } + } + } + } + + /** + * @brief Fill all the display with a color. + * + * Used for initialisation of the display with a background color. + * + * @param color Color code + */ + void fill(uint16_t color) { + for (uint16_t x = 0; x < width; x++) { + for (uint16_t y = 0; y < height; y++) { + drawPixel(x, y, color); + } + } + } + + void dim(uint8_t amount) { + for (uint16_t x = 0; x < width; x++) { + for (uint16_t y = 0; y < height; y++) { + drawPixel(x, y, dimColor(getPixel(x, y), amount)); + } + } + } + + void drawGraphics2D(int32_t offsetX, int32_t offsetY, Graphics2D* source) { + for (int32_t y = 0; y < source->getHeight(); y++) { + for (int32_t x = 0; x < source->getWidth(); x++) { + drawPixel(x + offsetX, y + offsetY, source->getPixel(x, y)); + } + } + } + + void drawGraphics2D(int32_t offsetX, int32_t offsetY, Graphics2D* source, int32_t sourceOffsetX, + int32_t sourceOffsetY, uint16_t sourceWidth, uint16_t sourceHeight) { + for (int32_t x = 0; x < sourceWidth; x++) { + for (int32_t y = 0; y < sourceHeight; y++) { + drawPixel(x + offsetX, y + offsetY, source->getPixel(x + sourceOffsetX, y + sourceOffsetY)); + } + } + } + + // draw scaled by 2x + void drawGraphics2D_2x(int32_t offsetX, int32_t offsetY, Graphics2D* source) { + for (int32_t x = 0; x < source->getWidth() * 2; x++) { + for (int32_t y = 0; y < source->getHeight() * 2; y++) { + drawPixel(x + offsetX, y + offsetY, source->getPixel(x / 2, y / 2)); + } + } + } + + // draw section scaled by 2x + void drawGraphics2D_2x(uint16_t offsetX, uint16_t offsetY, Graphics2D* source, uint16_t sourceOffsetX, + uint16_t sourceOffsetY, uint16_t sourceWidth, uint16_t sourceHeight) { + for (uint16_t x = 0; x < sourceWidth * 2; x++) { + for (uint16_t y = 0; y < sourceHeight * 2; y++) { + drawPixel(x + offsetX, y + offsetY, source->getPixel(sourceOffsetX + x / 2, sourceOffsetY + y / 2)); + } + } + } + +#ifdef ROTATE_LEGACY + // this rotate function is faster, but it has artifacts + void drawGraphics2D_rotatedLegacy(uint16_t offsetX, uint16_t offsetY, Graphics2D* source, uint16_t rotationX, + uint16_t rotationY, float angle) { + float cosA = cos(angle); + float sinA = sin(angle); + for (uint16_t x = 0; x < source->getWidth(); x++) { + for (uint16_t y = 0; y < source->getHeight(); y++) { + int32_t newX = (x - rotationX) * cosA + (y - rotationY) * sinA; + int32_t newY = (y - rotationY) * cosA - (x - rotationX) * sinA; + drawPixel(newX + offsetX, newY + offsetY, source->getPixel(x, y)); + } + } + } +#endif + + void drawGraphics2D_rotated(uint16_t offsetX, uint16_t offsetY, Graphics2D* source, uint16_t rx, uint16_t ry, + float angle) { + float cosA = cos(angle); + float sinA = sin(angle); + // rotateX = (x - rx) * cos(angle) + (y - ry) * sin(angle); + // rotateY = (y - ry) * cos(angle) - (x - rx) * sin(angle); + + // first calculate the bounding box of the new image + // // top left + int32_t tl_x = rotateX(0, 0, rx, ry, cosA, sinA); + int32_t tl_y = rotateY(0, 0, rx, ry, cosA, sinA); + // // top right + int32_t tr_x = rotateX(source->getWidth() - 1, 0, rx, ry, cosA, sinA); + int32_t tr_y = rotateY(source->getWidth() - 1, 0, rx, ry, cosA, sinA); + + // // bottom left + int32_t bl_x = rotateX(0, source->getHeight() - 1, rx, ry, cosA, sinA); + int32_t bl_y = rotateY(0, source->getHeight() - 1, rx, ry, cosA, sinA); + + // // bottom right + int32_t br_x = rotateX(source->getWidth(), source->getHeight(), rx, ry, cosA, sinA); + int32_t br_y = rotateY(source->getWidth(), source->getHeight(), rx, ry, cosA, sinA); + + // debug: draw rotated image + // this->drawLine(offsetX + tl_x, offsetY + tl_y, offsetX + tr_x, offsetY + tr_y, rgb565(255, 0, 0)); + // this->drawLine(offsetX + tr_x, offsetY + tr_y, offsetX + br_x, offsetY + br_y, rgb565(255, 0, 0)); + // this->drawLine(offsetX + bl_x, offsetY + bl_y, offsetX + br_x, offsetY + br_y, rgb565(255, 0, 0)); + // this->drawLine(offsetX + bl_x, offsetY + bl_y, offsetX + tl_x, offsetY + tl_y, rgb565(255, 0, 0)); + + // determine bounding box + int32_t boxX = min(tl_x, min(tr_x, min(bl_x, br_x))); + int32_t boxY = min(tl_y, min(tr_y, min(bl_y, br_y))); + int32_t boxW = max(tl_x, max(tr_x, max(bl_x, br_x))) - boxX; + int32_t boxH = max(tl_y, max(tr_y, max(bl_y, br_y))) - boxY; + + // debug: draw bounding box + // this->drawFrame(boxX + offsetX, boxY + offsetY, boxW, boxH, rgb565(0, 255, 0)); + cosA = cos(-angle); + sinA = sin(-angle); + for (int16_t x = boxX; x < boxX + boxW; x++) { + for (int16_t y = boxY; y < boxY + boxH; y++) { + if (pointInsideTriangle(x, y, tl_x, tl_y, tr_x, tr_y, br_x, br_y)) { + int16_t origX = rotateX(x, y, 0, 0, cosA, sinA); + int16_t origY = rotateY(x, y, 0, 0, cosA, sinA); + drawPixel(x + offsetX, y + offsetY, source->getPixel(origX + rx, origY + ry)); + } else if (pointInsideTriangle(x, y, tl_x, tl_y, bl_x, bl_y, br_x, br_y)) { + int16_t origX = rotateX(x, y, 0, 0, cosA, sinA); + int16_t origY = rotateY(x, y, 0, 0, cosA, sinA); + drawPixel(x + offsetX, y + offsetY, source->getPixel(origX + rx, origY + ry)); + } + } + } + } + + protected: + uint16_t** buffer; + DrawPixel* drawPixelCallback; + uint16_t* chunkXOffsets; + uint16_t* chunkWidths; + + /** + * @brief Width (in pixels) of the display frame + */ + uint16_t width; + + /** + * @brief Height (in pixels) of the display frame. + */ + uint16_t height; + + uint16_t maskColor; + uint16_t missingPixelColor; + bool maskEnabled; + uint8_t chunkHeight; + bool isRound; + bool alphaEnabled; + bool allocatePsram; + float alpha; +}; + +#endif diff --git a/include/gfx_2d_license.txt b/include/gfx_2d_license.txt new file mode 100644 index 000000000..3b35bc809 --- /dev/null +++ b/include/gfx_2d_license.txt @@ -0,0 +1,30 @@ +Most of the 2D Functions are taken from the U8G2 Project, see license below: + +Universal 8bit Graphics Library (http://code.google.com/p/u8g2/) + +Copyright (c) 2016, olikraus@gmail.com +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list + of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or other + materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND +CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/include/gfx_2d_print.h b/include/gfx_2d_print.h new file mode 100644 index 000000000..76badf2ea --- /dev/null +++ b/include/gfx_2d_print.h @@ -0,0 +1,566 @@ +#ifndef P3DT_GFX_2D_PRINT_H +#define P3DT_GFX_2D_PRINT_H + +// This is (still) a nasty copy paste job of Arduino_GFX.h, sorry, but it works. + +#ifdef OSW_EMULATOR +#include + +#include "FakeMe.h" +#include "FakePrint.h" +#include "Fakegfxfont.h" +#include "Fakeglcdfont.h" +#include "Fakepgmspace.h" +#else +#include +#include + +#include "Print.h" +#include "gfxfont.h" +#include "font/glcdfont.h" +#endif + +#include "fonts/ows_font_CEI_8859-15.cpp" +#include "gfx_2d.h" +#include "gfx_util.h" +#include "math_angles.h" + + +#ifdef OSW_EMULATOR +inline GFXglyph* pgm_read_glyph_ptr(const GFXfont* gfxFont, uint8_t c) { +#ifdef __AVR__ + return &(((GFXglyph*)pgm_read_pointer(&gfxFont->glyph))[c]); +#else + // expression in __AVR__ section may generate "dereferencing type-punned pointer will break strict-aliasing rules" + // warning In fact, on other platforms (such as STM32) there is no need to do this pointer magic as program memory may + // be read in a usual way So expression may be simplified + return gfxFont->glyph + c; +#endif //__AVR__ +} +inline uint8_t* pgm_read_bitmap_ptr(const GFXfont* gfxFont) { +#ifdef __AVR__ + return (uint8_t*)pgm_read_pointer(&gfxFont->bitmap); +#else + // expression in __AVR__ section generates "dereferencing type-punned pointer will break strict-aliasing rules" + // warning In fact, on other platforms (such as STM32) there is no need to do this pointer magic as program memory may + // be read in a usual way So expression may be simplified + return gfxFont->bitmap; +#endif //__AVR__ +} +#endif + +class Graphics2DPrint : public Graphics2D, public Print { + public: + Graphics2DPrint(uint16_t w_, uint16_t h_, uint8_t chunkHeight_, bool isRound_ = false, bool allocatePsram_ = false) + : Graphics2D(w_, h_, chunkHeight_, isRound_, allocatePsram_) { + cursor_x = 0; + cursor_y = 0; + _max_x = w_ - 1; ///< x zero base bound + _max_y = h_ - 1; ///< y zero base bound + textcolor = rgb565(255, 255, 255); + textbgcolor = rgb565(0, 0, 0); + textsize_x = textsize_y = 1; + // TODO: fix text wrapping on single character prints + // wrap = true; + _cp437 = false; + gfxFont = nullptr; + text_x_alignment = _text_alignment::LEFT; + text_y_alignment = _text_alignment::LEFT; + text_pixel_margin = 0; // unused + _rotation = 0; // unused + } + + // helper functions + size_t getTextLength(const uint8_t* buffer, size_t size) const { + size_t string_size = 0; + while (size--) { + string_size += getCharWidth(*buffer++); + } + return string_size; + } + + size_t getCharWidth(const uint8_t tempC) const { + if (tempC == '\n') { + return 0; + } + + if (gfxFont == nullptr) { + return 6; // basic font width. + } else { + uint8_t first = pgm_read_byte(&gfxFont->first); + if ((tempC >= first) && (tempC <= (uint8_t)pgm_read_byte(&gfxFont->last))) { + GFXglyph* glyph = pgm_read_glyph_ptr(gfxFont, tempC - first); + return pgm_read_byte(&glyph->xAdvance); + } + } + return 0; + } + + size_t getCharHeight(const uint8_t tempC) const { + if (tempC == '\n') { + return 0; + } + + if (gfxFont == nullptr) { + return 8; // basic font height. + } else { + return pgm_read_byte(&gfxFont->yAdvance); + } + return 0; + } + + /*! + @brief Draw a single character + @param x Bottom left corner x coordinate + @param y Bottom left corner y coordinate + @param c The 8-bit font-indexed character (likely ascii) + @param color 16-bit 5-6-5 Color to draw character with + @param bg 16-bit 5-6-5 Color to fill background with (if same as color, no background) + */ + void drawChar(int16_t x, int16_t y, unsigned char c, uint16_t color, uint16_t bg) { + uint16_t block_w; + uint16_t block_h; + + if (!gfxFont) { // 'Classic' built-in font + block_w = 6 * textsize_x; + block_h = 8 * textsize_y; + if ((x > _max_x) || // Clip right + (y > _max_y) || // Clip bottom + ((x + block_w - 1) < 0) || // Clip left + ((y + block_h - 1) < 0) // Clip top + ) { + return; + } + + if (!_cp437 && (c >= 176)) { + c++; // Handle 'classic' charset behavior + } + + // Not required: startWrite(); + for (int8_t i = 0; i < 5; i++) { // Char bitmap = 5 columns + uint8_t line = pgm_read_byte(&OSWfont[c * 5 + i]); + for (int8_t j = 0; j < 8; j++, line >>= 1) { + if (line & 1) { + if (textsize_x == 1 && textsize_y == 1) { + drawPixel(x + i, y + j, color); + } else { + fillFrame(x + i * textsize_x, y + j * textsize_y, textsize_x - text_pixel_margin, + textsize_y - text_pixel_margin, color); + } + } else if (bg != color) { + if (textsize_x == 1 && textsize_y == 1) { + drawPixel(x + i, y + j, bg); + } else { + fillFrame(x + i * textsize_x, y + j * textsize_y, textsize_x - text_pixel_margin, + textsize_y - text_pixel_margin, bg); + } + } + } + } + if (bg != color) { // If opaque, draw vertical line for last column + if (textsize_x == 1 && textsize_y == 1) { + drawVLine(x + 5, y, 8, bg); + } else { + fillFrame(x + 5 * textsize_x, y, textsize_x, 8 * textsize_y, bg); + } + } + // Not required: endWrite(); + } else { // Custom font + // Character is assumed previously filtered by write() to eliminate + // newlines, returns, non-printable characters, etc. Calling + // drawChar() directly with 'bad' characters of font may cause mayhem! + + c -= (uint8_t)pgm_read_byte(&gfxFont->first); + GFXglyph* glyph = pgm_read_glyph_ptr(gfxFont, c); + uint8_t* bitmap = pgm_read_bitmap_ptr(gfxFont); + + uint16_t bo = pgm_read_word(&glyph->bitmapOffset); + uint8_t w = pgm_read_byte(&glyph->width), h = pgm_read_byte(&glyph->height), + xAdvance = pgm_read_byte(&glyph->xAdvance), yAdvance = pgm_read_byte(&gfxFont->yAdvance), + baseline = yAdvance * 2 / 3; // TODO: baseline is an arbitrary currently, may be define in font file + int8_t xo = pgm_read_byte(&glyph->xOffset), yo = pgm_read_byte(&glyph->yOffset); + uint8_t xx, yy, bits = 0, bit = 0; + int16_t xo16 = 0, yo16 = 0; + + if (xAdvance < w) { + xAdvance = w; // Don't know why it exists + } + + if (textsize_x > 1 || textsize_y > 1) { + xo16 = (unsigned char)xo; + yo16 = (unsigned char)yo; + } + + block_w = xAdvance * textsize_x; + block_h = yAdvance * textsize_y; + if ((x > _max_x) || // Clip right + (y > _max_y) || // Clip bottom + ((x + block_w - 1) < 0) || // Clip left + ((y + block_h - 1) < 0) // Clip top + ) { + return; + } + + // NOTE: Different from Adafruit_GFX design, Adruino_GFX also cater background. + // Since it may introduce many ugly output, it should limited using on mono font only. + // Not required: startWrite(); + if (bg != color) { // have background color + fillFrame(x, y - (baseline * textsize_y), block_w, block_h, bg); + } + for (yy = 0; yy < h; yy++) { + for (xx = 0; xx < w; xx++) { + if (!(bit++ & 7)) { + bits = pgm_read_byte(&bitmap[bo++]); + } + if (bits & 0x80) { + if (textsize_x == 1 && textsize_y == 1) { + drawPixel(x + xo + xx, y + yo + yy, color); + } else { + fillFrame(x + (xo16 + xx) * textsize_x, y + (yo16 + yy) * textsize_y, textsize_x - text_pixel_margin, + textsize_y - text_pixel_margin, color); + } + } + bits <<= 1; + } + } + // Not required: endWrite(); + } // End classic vs custom font + } + + // manage writing to buffer and virtual from print header + size_t write(uint8_t c) override { + // newline can only happen with direct function calls + + if (!gfxFont) { // 'Classic' built-in font + if (c == '\n') { // Newline? + cursor_x = 0; // Reset x to zero, + cursor_y += textsize_y * 8; // advance y one line + } else if (c != '\r') { // Ignore carriage returns + drawChar(cursor_x, cursor_y - 7 * (textsize_y), c, textcolor, textbgcolor); + cursor_x += textsize_x * 6; // Advance x one char + } + } else { // Custom font + if (c == '\n') { + cursor_x = 0; + cursor_y += (int16_t)textsize_y * (uint8_t)pgm_read_byte(&gfxFont->yAdvance); + } else if (c != '\r') { + uint8_t first = pgm_read_byte(&gfxFont->first); + if ((c >= first) && (c <= (uint8_t)pgm_read_byte(&gfxFont->last))) { + GFXglyph* glyph = pgm_read_glyph_ptr(gfxFont, c - first); + drawChar(cursor_x, cursor_y, c, textcolor, textbgcolor); + cursor_x += textsize_x * pgm_read_byte(&glyph->xAdvance); + } + } + } + return 1; + } + + size_t write(const uint8_t* buffer, size_t size) override { + // check if it fits && check if there is a /n in the text. + int16_t temp_cursor_x = cursor_x; + // int16_t space = 0; + // TODO: fix text wrapping on single character prints + // if (wrap) { + // if (text_x_alignment == _text_alignment::LEFT) { + // space = width - temp_cursor_x; + // } else if (text_x_alignment == _text_alignment::RIGHT) { + // space = temp_cursor_x; + // } else if (text_x_alignment == _text_alignment::CENTER) { + // if (temp_cursor_x * 2 > width) { + // space = (width - temp_cursor_x) * 2; + // } else { + // space = temp_cursor_x * 2; + // } + // } + // } + + size_t count = 0; // to keep track of the buffer + uint16_t msg_length = 0; // keep track of the message length for wrap + for (size_t i = 0; i < size; i++) { + if (buffer[i] == '\n') { + if ((i - count) > 0) { + write_nocheck(&buffer[count], (i - count), false); + } + write('\n'); + cursor_x = temp_cursor_x; + count = i + 1; + msg_length = 0; + } + msg_length += getCharWidth(buffer[i]); + + // TODO: fix text wrapping on single character prints + // if (wrap && msg_length > space) { // wrap + // write_nocheck(&buffer[count], (i - count), false); + // write('\n'); + // cursor_x = temp_cursor_x; + // count = i; + // i--; // decrease it one to include the "to large" character + // msg_length = 0; + // } + } + + if (size == 1) { // needed for single characters + write_nocheck(&buffer[count], ((size)-count), true); + } else if (count != size - 1) { // needed for center when user doesn't provide an "\n" + write_nocheck(&buffer[count], ((size)-count), true); + } + + return count; + } + size_t write_nocheck(const uint8_t* buffer, size_t size, bool final) { + //--> remember the cursor position before printing + int16_t temp_cursor_x = cursor_x; + int16_t temp_cursor_y = cursor_y; + + //---> check x alignment + if (text_x_alignment == _text_alignment::RIGHT) { + const size_t msg_length = getTextLength(buffer, size); + cursor_x -= msg_length * textsize_x; + temp_cursor_x = cursor_x; + } else if (text_x_alignment == _text_alignment::CENTER) { + const size_t msg_length = getTextLength(buffer, size); + cursor_x -= msg_length * textsize_x / 2; + } + + //-->check y alignment + if (text_y_alignment == _text_alignment::CENTER) { + if (!gfxFont) + cursor_y += (int16_t)textsize_y * 8 / 2; + else { + const unsigned char c = '1' - (uint8_t)pgm_read_byte(&gfxFont->first); + GFXglyph* glyph_temp = pgm_read_glyph_ptr(gfxFont, c); + cursor_y += (int16_t)textsize_y * (glyph_temp->height / 2); + } + } else if (text_y_alignment == _text_alignment::RIGHT) { // in other words: under baseline + if (!gfxFont) + cursor_y += (int16_t)textsize_y * 8; + else { + const unsigned char c = '1' - (uint8_t)pgm_read_byte(&gfxFont->first); + GFXglyph* glyphtemp = pgm_read_glyph_ptr(gfxFont, c); + cursor_y += (int16_t)textsize_y * (glyphtemp->height); + } + } + + // then actually write the buffer. + size_t n = 0; + while (size--) { + n += write(*buffer++); + } + + //--> then fix the cursor x alignment + if (text_x_alignment == _text_alignment::RIGHT) { + cursor_x = temp_cursor_x; + } else if (text_x_alignment == _text_alignment::CENTER) { + if (final && buffer[size - 1] != '\n') { + if (!gfxFont) { + if (cursor_y == temp_cursor_y) cursor_y = temp_cursor_y + textsize_y * 8; // advance y one line + } else { + if (cursor_y == temp_cursor_y) + cursor_y = temp_cursor_y + textsize_y * gfxFont->yAdvance; // advance y one line + } + temp_cursor_y = cursor_y; + } + cursor_x = temp_cursor_x; + } + + //--> then fix the cursor y alignment + if (text_y_alignment != _text_alignment::LEFT) { + cursor_y = temp_cursor_y; + } + + return n; + } + + /** + * Display a number with a minimum number of digits. + * + * Example : yourNumer = 3 and numDigits = 4 display 0003 + * + * @param yourNumber number to display + * @param numDigits minimum number of digit to display + */ + void printDecimal(long yourNumber, int numDigits) { + for (int i = 1; i < numDigits; i++) { + if (yourNumber < pow(10, i)) { + print("0"); + } + } + print(yourNumber); + } + /** + @param str String parameter you need 'foo' -> E.g char foo[] = "index"; + @param pos Nth count string + @param directionHead string cut head + + */ + char* slice(char* str, int pos, bool directionHead = false) { + uint8_t len = strlen(str); + + if (len > abs(pos) && 0 != pos) { + if(directionHead) { + if (pos > 0) + str[pos] = 0; // 3 : (Hell)o, World -> Hell + else + str = str - pos; // -3 : Hel(lo, World) -> lo, World + } else { + if (pos > 0) + str = str + (len - pos); // 3 : Hello, Wo(rld) -> rld + else + str[len - abs(pos)] = '\0'; // -3 : (Hello, Wo)rld -> Hello, Wo + } + } + return str; + } + + // set alignment options + void setTextCenterAligned() { + text_x_alignment = _text_alignment::CENTER; + } + void setTextLeftAligned() { + text_x_alignment = _text_alignment::LEFT; + } + void setTextRightAligned() { + text_x_alignment = _text_alignment::RIGHT; + } + void setTextMiddleAligned() { + text_y_alignment = _text_alignment::CENTER; + } + void setTextBottomAligned() { + text_y_alignment = _text_alignment::LEFT; + } + void setTextTopAligned() { + text_y_alignment = _text_alignment::RIGHT; + } + + // set font options + void clearFont() { + setFont(nullptr); + } + + /** + * @brief Can defined a specific font to use. + * + * WARNING : you have to put a resetFont() when you want to use an other font. + * If you not this font will be used for all the characters displayed + * + * @param f + */ + void setFont(const GFXfont* f) { + gfxFont = (GFXfont*)f; + } + + /** + * @brief Set the text size. + * + * Size "1" mean a size of 8px height and 6 px width. + * Size "2" mean a double size => 18px height x 12px width + * + * @param s Size of the text + */ + void setTextSize(uint8_t s) { + setTextSize(s, s, 0); + } + + /** + * @brief Set the x and y factor size to apply + * + * Example : setTextSize(3,2) will set the text size to a block of 3*8=24px height and 2x6px=12px width + * + * @param s_x x/width size factor + * @param s_y y/height size factor + */ + void setTextSize(uint8_t s_x, uint8_t s_y) { + setTextSize(s_x, s_y, 0); + } + + void setTextSize(uint8_t s_x, uint8_t s_y, uint8_t pixel_margin) { + text_pixel_margin = ((pixel_margin < s_x) && (pixel_margin < s_y)) ? pixel_margin : 0; + textsize_x = (s_x > 0) ? s_x : 1; + textsize_y = (s_y > 0) ? s_y : 1; + } + + void setTextColor(uint16_t c) { + textcolor = textbgcolor = c; + } + void setTextColor(uint16_t c, uint16_t bg) { + textcolor = c; + textbgcolor = bg; + } + // TODO: fix text wrapping on single character prints + // void setTextWrap(bool w) { wrap = w; } + + // cursor functions + /** + * @brief Set the text cursor position. + * + * The cursor define le left-bottom position of the caracter. + * + * @param x x-axis of the cursor. + * @param y y-axis of the cursor. + */ + void setTextCursor(int16_t x, int16_t y) { + cursor_x = x; + cursor_y = y; + } + int16_t getTextCursorX() const { + return cursor_x; + } + int16_t getTextCursorY() const { + return cursor_y; + }; + void cp437(bool x = true) { + _cp437 = x; + } + + /*! + @brief Get Nth coordinate by font size (get coord of font X axis) + @param numChars Nth width + */ + uint16_t getTextOfsetColumns(const float numChars) const { // works with default font only + return numChars * getCharWidth(' ') * textsize_x; + } + /*! + @brief Get Nth coordinate by font size (get coord of font Y axis) + @param numRows Nth height + */ + uint16_t getTextOfsetRows(const float numRows) const { // works with default font only + return numRows * getCharHeight(' ') * textsize_y; + } + + void resetText() { + setTextSize(1); + clearFont(); + setTextLeftAligned(); + setTextBottomAligned(); + } + + protected: + int16_t _max_x; + int16_t _max_y; + + /** + * Distance to the left of the screen (in pixels) of the cursor. + **/ + int16_t cursor_x; + /** + * Distance to the top of the screen (in pixels) of the cursor. + **/ + int16_t cursor_y; + uint16_t textcolor; + uint16_t textbgcolor; + uint8_t textsize_x; + uint8_t textsize_y; + uint8_t text_pixel_margin; + uint8_t _rotation; + enum _text_alignment { RIGHT = 0, LEFT, CENTER }; + _text_alignment text_x_alignment; + _text_alignment text_y_alignment; + // TODO: fix text wrapping on single character prints + // bool wrap; + bool _cp437; + + public: + GFXfont* gfxFont; +}; + +#endif diff --git a/include/gfx_util.h b/include/gfx_util.h new file mode 100644 index 000000000..a2227bd54 --- /dev/null +++ b/include/gfx_util.h @@ -0,0 +1,29 @@ +#ifndef P3DT_GFX_UTIL_H +#define P3DT_GFX_UTIL_H + +#include + +uint16_t rgb565(uint8_t red, uint8_t green, uint8_t blue); +uint32_t rgb888(uint8_t red, uint8_t green, uint8_t blue); + +uint32_t rgb565to888(uint16_t rgb565); +uint16_t rgb888to565(uint32_t rgb888); + +uint8_t rgb565_red(uint16_t rgb565); +uint8_t rgb565_green(uint16_t rgb565); +uint8_t rgb565_blue(uint16_t rgb565); + +uint8_t rgb888_red(uint32_t rgb888); +uint8_t rgb888_green(uint32_t rgb888); +uint8_t rgb888_blue(uint32_t rgb888); + +uint16_t blend(uint16_t target, uint16_t source, float alpha); +uint16_t dimColor(uint16_t oc, uint8_t amount); +uint16_t changeColor(uint16_t oc, float amount); + +void hsvToRgb(const unsigned char& h, const unsigned char& s, const unsigned char& v, unsigned char& r, + unsigned char& g, unsigned char& b); + +void rgbToHsv(const unsigned char& r, const unsigned char& g, const unsigned char& b, unsigned char& h, + unsigned char& s, unsigned char& v); +#endif diff --git a/include/hal/devices.h b/include/hal/devices.h index 811520246..76f70a6ad 100644 --- a/include/hal/devices.h +++ b/include/hal/devices.h @@ -3,10 +3,18 @@ #include #include #include +#if OSW_PLATFORM_HARDWARE_BMA400 == 1 #include +#endif +#if OSW_PLATFORM_HARDWARE_QMC5883L == 1 #include +#endif +#if OSW_PLATFORM_HARDWARE_BME280 == 1 #include +#endif +#if OSW_PLATFORM_HARDWARE_DS3231MZ == 1 #include +#endif #include #include diff --git a/include/hal/environment.h b/include/hal/environment.h index 5f352be4d..490315679 100644 --- a/include/hal/environment.h +++ b/include/hal/environment.h @@ -36,6 +36,7 @@ class OswHal::Environment { // Statistics: Steps void setupStepStatistics(); uint32_t getStepsToday(); + void resetStepCount(); uint32_t getStepsTotal(); uint32_t getStepsTotalWeek(); #ifdef OSW_FEATURE_STATS_STEPS diff --git a/include/locales/cs-CZ.h b/include/locales/cs-CZ.h index 52ca1a8e4..e20dc4686 100644 --- a/include/locales/cs-CZ.h +++ b/include/locales/cs-CZ.h @@ -20,6 +20,8 @@ #define LANG_WEBSRV_TITLE "Configuration" #define LANG_WEBSRV_USER "User:" #define LANG_WEBSRV_PASS "Password:" +#define LANG_WEBSRV_AP_PASSWORD_ON "AutoAP PW ON" +#define LANG_WEBSRV_AP_PASSWORD_OFF "AutoAP PW OFF" // App: Watchface Binary #define LANG_WATCHFACE_BINARY_STEPS "steps" diff --git a/include/locales/de-DE.h b/include/locales/de-DE.h index d74467167..ba5606435 100644 --- a/include/locales/de-DE.h +++ b/include/locales/de-DE.h @@ -21,6 +21,8 @@ #define LANG_WEBSRV_TITLE "Einstellungen" #define LANG_WEBSRV_USER "Nutzer:" #define LANG_WEBSRV_PASS "Passwort:" +#define LANG_WEBSRV_AP_PASSWORD_ON "AutoAP PW an" +#define LANG_WEBSRV_AP_PASSWORD_OFF "AutoAP PW aus" // App: Watchface Binary #define LANG_WATCHFACE_BINARY_STEPS "Schritte" diff --git a/include/locales/en-US.h b/include/locales/en-US.h index b75de7123..72d6b7692 100644 --- a/include/locales/en-US.h +++ b/include/locales/en-US.h @@ -21,6 +21,8 @@ #define LANG_WEBSRV_TITLE "Configuration" #define LANG_WEBSRV_USER "User:" #define LANG_WEBSRV_PASS "Password:" +#define LANG_WEBSRV_AP_PASSWORD_ON "AutoAP PW ON" +#define LANG_WEBSRV_AP_PASSWORD_OFF "AutoAP PW OFF" // App: Watchface Binary #define LANG_WATCHFACE_BINARY_STEPS "steps" diff --git a/include/locales/fr-FR.h b/include/locales/fr-FR.h index 1355946cd..c76ccf24e 100644 --- a/include/locales/fr-FR.h +++ b/include/locales/fr-FR.h @@ -21,6 +21,8 @@ #define LANG_WEBSRV_TITLE "Configuration UI" #define LANG_WEBSRV_USER "User:" #define LANG_WEBSRV_PASS "Password:" +#define LANG_WEBSRV_AP_PASSWORD_ON "AutoAP PW ON" +#define LANG_WEBSRV_AP_PASSWORD_OFF "AutoAP PW OFF" // App: Watchface Binary #define LANG_WATCHFACE_BINARY_STEPS "steps" diff --git a/include/locales/hu-HU.h b/include/locales/hu-HU.h index 874b2685d..8896992eb 100644 --- a/include/locales/hu-HU.h +++ b/include/locales/hu-HU.h @@ -21,6 +21,8 @@ #define LANG_WEBSRV_TITLE "Configuration UI" #define LANG_WEBSRV_USER "User:" #define LANG_WEBSRV_PASS "Password:" +#define LANG_WEBSRV_AP_PASSWORD_ON "AutoAP PW ON" +#define LANG_WEBSRV_AP_PASSWORD_OFF "AutoAP PW OFF" // App: Watchface Binary #define LANG_WATCHFACE_BINARY_STEPS "steps" diff --git a/include/locales/it-IT.h b/include/locales/it-IT.h index 98cb94f7a..844a48275 100644 --- a/include/locales/it-IT.h +++ b/include/locales/it-IT.h @@ -21,6 +21,8 @@ #define LANG_WEBSRV_TITLE "Configuration UI" #define LANG_WEBSRV_USER "User:" #define LANG_WEBSRV_PASS "Password:" +#define LANG_WEBSRV_AP_PASSWORD_ON "AutoAP PW ON" +#define LANG_WEBSRV_AP_PASSWORD_OFF "AutoAP PW OFF" // App: Watchface Binary #define LANG_WATCHFACE_BINARY_STEPS "steps" diff --git a/include/locales/ko-KR.h b/include/locales/ko-KR.h index 6c2ddea8e..f37c1af90 100644 --- a/include/locales/ko-KR.h +++ b/include/locales/ko-KR.h @@ -23,6 +23,8 @@ #define LANG_WEBSRV_TITLE "Configuration" #define LANG_WEBSRV_USER "User:" #define LANG_WEBSRV_PASS "Password:" +#define LANG_WEBSRV_AP_PASSWORD_ON "AutoAP PW ON" +#define LANG_WEBSRV_AP_PASSWORD_OFF "AutoAP PW OFF" // App: Watchface Binary #define LANG_WATCHFACE_BINARY_STEPS "steps" diff --git a/include/math_angles.h b/include/math_angles.h new file mode 100644 index 000000000..2675766b4 --- /dev/null +++ b/include/math_angles.h @@ -0,0 +1,56 @@ +#ifndef GFX_ANGLES_H +#define GFX_ANGLES_H + +#include + +/** + * @brief Find the x-axis point which is at a distance r and an angle d of a point C(cx,cy). + * + * 0 degrees ist 12 o'clock + * + * This function can be used to find coordonnates of the extremity of the clock hand from the center + * + * @param cx x value of the initial point + * @param r radius + * @param d angle in degrees (0° is 12 o'clock) + * @return float + */ +float rpx(float cx, float x, float r); + + +/** + * Find the y-axis of a point which is at a distance r and an angle d of a point C(cx,cy). + * + * 0 degrees ist 12 o'clock + * This function can be used to find coordonnates of the extremity of the clock hand from the center + * + * @param cy y value of the initial point + * @param r radius + * @param d angle in degrees (0° is 12 o'clock) + * @return float + */ +float rpy(float cy, float y, float r); + +int32_t rotateX(int32_t x, int32_t y, int32_t rx, int32_t ry, float cosA, float sinA); +int32_t rotateY(int32_t x, int32_t y, int32_t rx, int32_t ry, float cosA, float sinA); +int32_t rotateX(int32_t x, int32_t y, int32_t rx, int32_t ry, float a); +int32_t rotateY(int32_t x, int32_t y, int32_t rx, int32_t ry, float a); + +/** + * Convert seconds in degrees. + * + * 0 seconds = 0° / 15 seconds = 90° ... + * + * @param seconds seconds to convert + * @return float angle in degrees + */ +float s2d(long seconds); + +// minutes to degrees (0-360) +float m2d(long seconds); + +// hours to degrees (0-360) +float h2d(long seconds); + +bool pointInsideTriangle(float px, float py, float x1, float y1, float x2, float y2, float x3, float y3); +#endif \ No newline at end of file diff --git a/include/math_osm.h b/include/math_osm.h new file mode 100644 index 000000000..cc07886bc --- /dev/null +++ b/include/math_osm.h @@ -0,0 +1,23 @@ +#ifndef OSM_H +#define OSM_H + +#include + +// source: https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#C.2FC.2B.2B + +// we return float here, because we need the fraction + +float lon2tilex(float lon, uint8_t z); + +float lat2tiley(float lat, uint8_t z); + +// helper function to get the offset within the tile +int32_t tileOffset(float tilex); + +float tilex2lon(float x, uint8_t z); + +float tiley2lat(float y, uint8_t z); + +float getTileResolution(float lat, uint8_t z); + +#endif \ No newline at end of file diff --git a/include/osm_render.h b/include/osm_render.h new file mode 100644 index 000000000..30027c644 --- /dev/null +++ b/include/osm_render.h @@ -0,0 +1,57 @@ +#ifndef OSM_RENDER_H +#define OSM_RENDER_H + +#include + +#include "gfx_2d.h" +#include "math_osm.h" + +#define TILE_W 256 +#define TILE_H 256 +#define TILE_CHUNK_H 16 + +typedef void (*loadTile)(Graphics2D* target, int8_t z, float tilex, float tiley, int32_t offsetx, int32_t offsety); + +class BufferedTile { + public: + BufferedTile(bool inPsram) { + gfx = new Graphics2D(TILE_W, TILE_H, TILE_CHUNK_H, false /* not round */, inPsram /* but in psram*/); + lastUsed = 0; + }; + ~BufferedTile() { + delete gfx; + } + + void loadTile(loadTile loadTileFn, uint32_t tileX, uint32_t tileY, uint8_t tileZ) { + loadTileFn(gfx, tileZ, tileX, tileY, 0, 0); + _tileX = tileX; + _tileY = tileY; + _tileZ = tileZ; + lastUsed = millis(); + } + + bool hasTile(uint32_t tileX, uint32_t tileY, uint8_t tileZ) { + return tileZ == _tileZ && tileX == _tileX && tileY == _tileY && lastUsed != 0; + } + + Graphics2D* getGraphics() { + lastUsed = millis(); + return gfx; + } + + unsigned long getLastUsed() { + return lastUsed; + } + + private: + Graphics2D* gfx; + uint8_t _tileZ; + uint32_t _tileX; + uint32_t _tileY; + unsigned long lastUsed; +}; + +void drawTiles(Graphics2D* target, loadTile loadTileFn, float lat, float lon, uint8_t z); +void drawTilesBuffered(BufferedTile** buffer, uint8_t bufferLength, Graphics2D* target, // + loadTile loadTileFn, float lat, float lon, uint8_t z); +#endif diff --git a/include/osw_config.h b/include/osw_config.h index aaa646e76..0f2ac6f21 100644 --- a/include/osw_config.h +++ b/include/osw_config.h @@ -1,6 +1,9 @@ #ifndef OSW_CONFIG_H #define OSW_CONFIG_H +#ifdef OSW_EMULATOR +#include // Only used for Serial.* +#endif #include #include "config_defaults.h" @@ -43,6 +46,7 @@ class OswConfig { int getBootCount(); String getConfigJSON(); void parseDataJSON(const char* json); + void notifyChange(); _OSW_CONFIG_SET_GET(int8_t, getChar, putChar) _OSW_CONFIG_SET_GET(uint8_t, getUChar, putUChar) diff --git a/include/osw_config_keys.h b/include/osw_config_keys.h index ab75b8426..68107d2bb 100644 --- a/include/osw_config_keys.h +++ b/include/osw_config_keys.h @@ -24,6 +24,8 @@ class OswConfigKeyRGB; namespace OswConfigAllKeys { #ifdef OSW_FEATURE_WIFI extern OswConfigKeyString hostname; +extern OswConfigKeyBool hostPasswordEnabled; +extern OswConfigKeyPassword hostPass; #ifdef OSW_FEATURE_WIFI_ONBOOT extern OswConfigKeyBool wifiBootEnabled; #endif @@ -75,6 +77,23 @@ extern OswConfigKeyBool settingDisplayStepsGoal; extern OswConfigKeyDropDown settingDisplayDefaultWatchface; } // namespace OswConfigAllKeys +/** + * @brief This enum holds all known type-ids of the web interface + * + */ +enum class OswConfigKeyTypedUIType: char { + STRING = 'S', + PASSWORD = 'P', + DROPDOWN = 'd', + ULONG = 'L', + INT = 'I', + SHORT = 'i', + RGB = 'R', + BOOL = 'C', + DOUBLE = 'D', + FLOAT = 'F' +}; + /** * Config key interface - this enforces the key must * be serializeale from and to Strings (used for the data.json) @@ -83,7 +102,7 @@ extern OswConfigKeyDropDown settingDisplayDefaultWatchface; */ class OswConfigKey { public: - OswConfigKey(const char* cType, const char* id, const char* section, const char* label, const char* help) + OswConfigKey(const OswConfigKeyTypedUIType& cType, const char* id, const char* section, const char* label, const char* help) : id(id), section(section), label(label), help(help), type(cType) {} virtual const String toString() const = 0; virtual const String toDefaultString() const = 0; @@ -92,7 +111,7 @@ class OswConfigKey { const char* section; const char* label; const char* help; - const char* type; + const OswConfigKeyTypedUIType type; protected: virtual void loadValueFromNVS() = 0; @@ -110,7 +129,7 @@ extern OswConfigKey* oswConfigKeys[]; template class OswConfigKeyTyped : public OswConfigKey { public: - OswConfigKeyTyped(const char* configUiType, const char* id, const char* section, const char* label, const char* help, + OswConfigKeyTyped(const OswConfigKeyTypedUIType& configUiType, const char* id, const char* section, const char* label, const char* help, const T def) : OswConfigKey(configUiType, id, section, label, help), def(def) { // Nothing in here @@ -132,7 +151,7 @@ class OswConfigKeyTyped : public OswConfigKey { class OswConfigKeyString : public OswConfigKeyTyped { public: OswConfigKeyString(const char* id, const char* section, const char* label, const char* help, const String& def) - : OswConfigKeyTyped("S", id, section, label, help, String(def)) {} + : OswConfigKeyTyped(OswConfigKeyTypedUIType::STRING, id, section, label, help, String(def)) {} const String toDefaultString() const { return this->def; } @@ -158,7 +177,7 @@ class OswConfigKeyString : public OswConfigKeyTyped { class OswConfigKeyPassword : public OswConfigKeyTyped { public: OswConfigKeyPassword(const char* id, const char* section, const char* label, const char* help, const String& def) - : OswConfigKeyTyped("P", id, section, label, help, String(def)) {} + : OswConfigKeyTyped(OswConfigKeyTypedUIType::PASSWORD, id, section, label, help, String(def)) {} const String toDefaultString() const { return this->def; } @@ -184,7 +203,7 @@ class OswConfigKeyPassword : public OswConfigKeyTyped { class OswConfigKeyDropDown : public OswConfigKeyTyped { public: OswConfigKeyDropDown(const char* id, const char* section, const char* label, const char* help, const String& def) - : OswConfigKeyTyped("d", id, section, label, help, String(def)) {} + : OswConfigKeyTyped(OswConfigKeyTypedUIType::DROPDOWN, id, section, label, help, String(def)) {} const String toDefaultString() const { return this->def; } @@ -210,7 +229,7 @@ class OswConfigKeyUnsignedLong : public OswConfigKeyTyped { public: OswConfigKeyUnsignedLong(const char* id, const char* section, const char* label, const char* help, const unsigned long& def) - : OswConfigKeyTyped("L", id, section, label, help, def) {} + : OswConfigKeyTyped(OswConfigKeyTypedUIType::ULONG, id, section, label, help, def) {} const String toDefaultString() const { return String(this->def); } @@ -235,7 +254,7 @@ class OswConfigKeyUnsignedLong : public OswConfigKeyTyped { class OswConfigKeyInt : public OswConfigKeyTyped { public: OswConfigKeyInt(const char* id, const char* section, const char* label, const char* help, const int& def) - : OswConfigKeyTyped("I", id, section, label, help, def) {} + : OswConfigKeyTyped(OswConfigKeyTypedUIType::INT, id, section, label, help, def) {} const String toDefaultString() const { return String(this->def); } @@ -260,7 +279,7 @@ class OswConfigKeyInt : public OswConfigKeyTyped { class OswConfigKeyShort : public OswConfigKeyTyped { public: OswConfigKeyShort(const char* id, const char* section, const char* label, const char* help, const short& def) - : OswConfigKeyTyped("i", id, section, label, help, def) {} + : OswConfigKeyTyped(OswConfigKeyTypedUIType::SHORT, id, section, label, help, def) {} const String toDefaultString() const { return String(this->def); } @@ -285,7 +304,7 @@ class OswConfigKeyShort : public OswConfigKeyTyped { class OswConfigKeyRGB : public OswConfigKeyTyped { public: OswConfigKeyRGB(const char* id, const char* section, const char* label, const char* help, const uint32_t& def) - : OswConfigKeyTyped("R", id, section, label, help, def) {} + : OswConfigKeyTyped(OswConfigKeyTypedUIType::RGB, id, section, label, help, def) {} const String toDefaultString() const { return String(this->def); } @@ -310,7 +329,7 @@ class OswConfigKeyRGB : public OswConfigKeyTyped { class OswConfigKeyBool : public OswConfigKeyTyped { public: OswConfigKeyBool(const char* id, const char* section, const char* label, const char* help, const bool& def) - : OswConfigKeyTyped("C", id, section, label, help, def) {} + : OswConfigKeyTyped(OswConfigKeyTypedUIType::BOOL, id, section, label, help, def) {} const String toDefaultString() const { return String(this->def); } @@ -335,7 +354,7 @@ class OswConfigKeyBool : public OswConfigKeyTyped { class OswConfigKeyDouble : public OswConfigKeyTyped { public: OswConfigKeyDouble(const char* id, const char* section, const char* label, const char* help, const double& def) - : OswConfigKeyTyped("D", id, section, label, help, def) {} + : OswConfigKeyTyped(OswConfigKeyTypedUIType::DOUBLE, id, section, label, help, def) {} const String toDefaultString() const { return String(this->def); } @@ -360,7 +379,7 @@ class OswConfigKeyDouble : public OswConfigKeyTyped { class OswConfigKeyFloat : public OswConfigKeyTyped { public: OswConfigKeyFloat(const char* id, const char* section, const char* label, const char* help, const float& def) - : OswConfigKeyTyped("F", id, section, label, help, def) {} + : OswConfigKeyTyped(OswConfigKeyTypedUIType::FLOAT, id, section, label, help, def) {} const String toDefaultString() const { return String(this->def); } diff --git a/include/osw_hal.h b/include/osw_hal.h index ac8cef24d..7d0852153 100644 --- a/include/osw_hal.h +++ b/include/osw_hal.h @@ -2,12 +2,16 @@ #define OSW_HAL_H #include +#ifdef OSW_EMULATOR +#include +#else #include +#endif +#include "Arduino_Canvas_Graphics2D.h" #include #include #include OSW_TARGET_PLATFORM_HEADER -#include "Arduino_Canvas_Graphics2D.h" #include "hal/osw_filesystem.h" #include #include "osw_config_keys.h" @@ -27,6 +31,8 @@ enum Button { BUTTON_1 = 0, BUTTON_2 = 1, BUTTON_3 = 2 }; class OswHal { public: static OswHal* getInstance(); + static void resetInstance(); + class Devices; Devices* devices = nullptr; @@ -93,7 +99,6 @@ class OswHal { */ uint8_t screenBrightness(bool checkHardware = false); - Arduino_TFT* getArduino_TFT(void); Arduino_Canvas_Graphics2D* getCanvas(void); Graphics2DPrint* gfx(void); void flushCanvas(void); @@ -135,7 +140,7 @@ class OswHal { #endif // Power - boolean isCharging(void); + bool isCharging(void); uint16_t getBatteryRaw(const uint16_t numAvg = 8); // float getBatteryVoltage(void); void updatePowerStatistics(uint16_t currBattery); @@ -203,6 +208,8 @@ class OswHal { OswHal(FileSystemHal* fs); ~OswHal(); + Arduino_Canvas_Graphics2D* canvas = nullptr; + static OswHal* instance; OswTimeProvider* timeProvider = nullptr; unsigned long _screenOnSince; diff --git a/include/osw_pins.h b/include/osw_pins.h index ec1783be1..05a9aa2f3 100644 --- a/include/osw_pins.h +++ b/include/osw_pins.h @@ -47,4 +47,11 @@ #define BTN_1 0 #define BTN_2 10 #define BTN_3 13 +#endif + +// assign active LOW or HIGH states according to hardware +#if defined(GPS_EDITION_ROTATED) +#define BTN_STATE_ARRAY {HIGH, HIGH, LOW} +#else +#define BTN_STATE_ARRAY {LOW, HIGH, HIGH} #endif \ No newline at end of file diff --git a/include/osw_ui.h b/include/osw_ui.h index 58c56bd9c..9e545c038 100644 --- a/include/osw_ui.h +++ b/include/osw_ui.h @@ -30,6 +30,7 @@ class OswUI { float endValue = 0; time_t endTime = 0; }; + bool mEnableTargetFPS = true; OswUI(); void loop(OswAppSwitcher& mainAppSwitcher, uint16_t& mainAppIndex); diff --git a/include/platform/EMULATOR.h b/include/platform/EMULATOR.h new file mode 100644 index 000000000..f3b734ec5 --- /dev/null +++ b/include/platform/EMULATOR.h @@ -0,0 +1,22 @@ +#pragma once + +#define OSW_PLATFORM_ENVIRONMENT 1 +#define OSW_PLATFORM_ENVIRONMENT_TEMPERATURE 1 +#define OSW_PLATFORM_ENVIRONMENT_PRESSURE 1 +#define OSW_PLATFORM_ENVIRONMENT_HUMIDITY 1 +#define OSW_PLATFORM_ENVIRONMENT_MAGNETOMETER 1 +#define OSW_PLATFORM_ENVIRONMENT_ACCELEROMETER 1 + +//#define OSW_PLATFORM_HARDWARE_DS3231MZ 1 +//#define OSW_PLATFORM_HARDWARE_BMA400 1 +//#define OSW_PLATFORM_HARDWARE_BME280 0 +//#define OSW_PLATFORM_HARDWARE_QMC5883L 0 +#define OSW_PLATFORM_HARDWARE_VIRTUAL 1 + +//#define OSW_DEVICE_DS3231MZ_RTCINT 32 +//#define OSW_DEVICE_BMA400_INT1 34 +//#define OSW_DEVICE_BMA400_INT2 35 +//#define OSW_DEVICE_I2C_SCL 22 +//#define OSW_DEVICE_I2C_SDA 21 +#define OSW_DEVICE_TPS2115A_STATPWR 15 +#define OSW_DEVICE_ESP32_BATLVL 25 \ No newline at end of file diff --git a/include/services/OswServiceManager.h b/include/services/OswServiceManager.h index 2e800d7c1..ee1fb6df8 100644 --- a/include/services/OswServiceManager.h +++ b/include/services/OswServiceManager.h @@ -3,6 +3,10 @@ #include "osw_hal.h" #include "osw_service.h" +#ifdef OSW_EMULATOR +#include +#endif + class OswServiceManager { public: static OswServiceManager& getInstance() { @@ -25,7 +29,11 @@ class OswServiceManager { private: OswServiceManager() {}; void worker(); +#ifndef OSW_EMULATOR TaskHandle_t core0worker; +#else + std::thread* core0worker; +#endif bool active = false; OswServiceManager(OswServiceManager const&); diff --git a/include/services/OswServiceTaskBLECompanion.h b/include/services/OswServiceTaskBLECompanion.h index 4c0b9ddd9..bb68d05ad 100644 --- a/include/services/OswServiceTaskBLECompanion.h +++ b/include/services/OswServiceTaskBLECompanion.h @@ -1,6 +1,7 @@ #ifndef OSW_SERVICE_COMPANION_H #define OSW_SERVICE_COMPANION_H +#ifndef OSW_EMULATOR #include #include #include @@ -41,4 +42,5 @@ class OswServiceTaskBLECompanion : public OswServiceTask { friend class NotificationCallback; }; +#endif #endif \ No newline at end of file diff --git a/include/services/OswServiceTaskWiFi.h b/include/services/OswServiceTaskWiFi.h index d3ee3e16f..b3431d612 100644 --- a/include/services/OswServiceTaskWiFi.h +++ b/include/services/OswServiceTaskWiFi.h @@ -38,6 +38,7 @@ class OswServiceTaskWiFi : public OswServiceTask { bool isStationEnabled(); void enableStation(const String& password = String("")); void disableStation(); + void toggleAPPassword(); IPAddress getStationIP(); const String& getStationSSID() const; const String& getStationPassword() const; @@ -58,7 +59,7 @@ class OswServiceTaskWiFi : public OswServiceTask { bool m_enableStation = false; bool m_enabledMDNS = false; time_t m_enabledStationByAutoAP = 0; - const time_t m_enabledStationByAutoAPTimeout = 10 * 60; // Maximum allowed time for the auto ap to stay active - after that it ALLWAYS WILL TRY to reconnect + const time_t m_enabledStationByAutoAPTimeout = 10 * 60; // Maximum allowed time for the auto ap to stay active - after that it ALWAYS WILL TRY to reconnect bool m_queuedNTPUpdate = false; //Will be set to true it this feature is active bool m_waitingForNTPUpdate = false; String m_hostname; @@ -72,4 +73,4 @@ class OswServiceTaskWiFi : public OswServiceTask { }; #endif -#endif \ No newline at end of file +#endif diff --git a/lib/AnimatedGIF b/lib/AnimatedGIF index c1cd3d5ec..1d2fae5e4 160000 --- a/lib/AnimatedGIF +++ b/lib/AnimatedGIF @@ -1 +1 @@ -Subproject commit c1cd3d5ecbdf85be746e5ce01ea6c0e6163e681c +Subproject commit 1d2fae5e40b718e715f720b809acbab90ad6b55d diff --git a/lib/Arduino_GFX b/lib/Arduino_GFX index b8288bbe1..4aaf9074d 160000 --- a/lib/Arduino_GFX +++ b/lib/Arduino_GFX @@ -1 +1 @@ -Subproject commit b8288bbe1b1b41b7f92e5344923278508c85905c +Subproject commit 4aaf9074d5db1055c67f2d91bde1f4f113f7e5b6 diff --git a/lib/ESP32-BLE-Keyboard b/lib/ESP32-BLE-Keyboard index 928c09320..f8dd48521 160000 --- a/lib/ESP32-BLE-Keyboard +++ b/lib/ESP32-BLE-Keyboard @@ -1 +1 @@ -Subproject commit 928c09320e3e6e344a63640c7de21d52a20c1d47 +Subproject commit f8dd4852113a722a6b8dc8af987e94cf84d73ad5 diff --git a/lib/lib-open-smartwatch b/lib/lib-open-smartwatch deleted file mode 160000 index c6f0dbda4..000000000 --- a/lib/lib-open-smartwatch +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c6f0dbda470d71b01f0911008ee06ef89afe78c2 diff --git a/lib/pngle b/lib/pngle index 789289547..73631e900 160000 --- a/lib/pngle +++ b/lib/pngle @@ -1 +1 @@ -Subproject commit 789289547651ea4e69cf6fec2b054c083774d56d +Subproject commit 73631e9004b156e17a7e534cb3fba66e7657d206 diff --git a/screenshots/emulator_demo.png b/screenshots/emulator_demo.png new file mode 100644 index 000000000..caeebf390 Binary files /dev/null and b/screenshots/emulator_demo.png differ diff --git a/scripts/build/prebuild_info.py b/scripts/build/prebuild_info.py index 6684c8cd6..ebc9e4bcf 100644 --- a/scripts/build/prebuild_info.py +++ b/scripts/build/prebuild_info.py @@ -15,7 +15,7 @@ if gitAvailable: gitCommitHash = subprocess.Popen( - ['git', 'rev-parse', 'HEAD'], stdout=subprocess.PIPE).communicate()[0].decode('utf-8').strip()[:7] # Short hash + ['git', 'rev-parse', '--short', 'HEAD'], stdout=subprocess.PIPE).communicate()[0].decode('utf-8').strip()[:7] # Short hash gitCommitTime = subprocess.Popen( ['git', 'log', '-1', '--pretty=format:%cd', '--date=format:%Y-%m-%dT%H:%M:%S%z'], stdout=subprocess.PIPE).communicate()[0].decode('utf-8').strip() gitBranchName = subprocess.Popen( diff --git a/src/animations/anim_doom_fire.cpp b/src/animations/anim_doom_fire.cpp new file mode 100644 index 000000000..245413e24 --- /dev/null +++ b/src/animations/anim_doom_fire.cpp @@ -0,0 +1,49 @@ +#include "animations/anim_doom_fire.h" + +AnimDoomFire::AnimDoomFire() { + firePixels = new uint8_t* [240]; + for (int i = 0; i < 240; i++) + firePixels[i] = new uint8_t[240]; + setupFire(firePixels, 240, 240); +} + +AnimDoomFire::~AnimDoomFire() { + for (int i = 0; i < 240; i++) + delete[] firePixels[i]; + delete[] firePixels; +} + +void AnimDoomFire::setupFire(uint8_t** firePixels, uint16_t w, uint16_t h) { + for (uint8_t y = 0; y < h; y++) { + for (uint8_t x = 0; x < w; x++) { + // last row is hot + firePixels[y][x] = y == h - 1 ? 35 : 0; + } + } +} + +void AnimDoomFire::calcFire(uint8_t** firePixels, uint16_t w, uint16_t h,float* X,float* Y) { + for (uint8_t y = 0; y < h - 1; y++) { + for (uint8_t x = 0; x < w; x++) { + uint8_t wind = x + random(2) + (Y == nullptr ? 0 : (abs(*Y) / 15000)); + wind = wind >= w ? wind - w : wind; + uint8_t speed = y + random(2) + (X == nullptr ? 0:(abs(*X) / 15000)); + speed = speed >= h ? h - 1 : speed; + firePixels[y][x] = firePixels[speed][wind] - random(2); + firePixels[y][x] = firePixels[y][x] > 35 ? 0 : firePixels[y][x]; // fix overflow + } + } +} + +void AnimDoomFire::mapFire(uint8_t** firePixels, uint16_t fireW, uint16_t fireH, Graphics2D* graphics2d, uint16_t offsetX, uint16_t offsetY) { + for (uint8_t x = 0; x < fireW; x++) { + for (uint8_t y = 0; y < fireH; y++) { + graphics2d->drawPixel(x + offsetX, y + offsetY, doomColorMap[firePixels[y][x]]); + } + } +} + +void AnimDoomFire::loop(Graphics2DPrint* gfx,float* X, float* Y) { + calcFire(firePixels, 240, 240,X,Y); + mapFire(firePixels, 240, 240, gfx, 0, 0); +} \ No newline at end of file diff --git a/src/animations/anim_doom_fire_old.cpp b/src/animations/anim_doom_fire_old.cpp new file mode 100644 index 000000000..0e8032b51 --- /dev/null +++ b/src/animations/anim_doom_fire_old.cpp @@ -0,0 +1,76 @@ +#include "animations/anim_doom_fire_old.h" + +#include "gfx_2d.h" +#include "gfx_util.h" +// see: https://p3dt.net/post/2019/01/05/playing-with-doom.html + +const uint16_t doomColorMap[36] = { + // + rgb565(0x00, 0x00, 0x00), // #000000 + rgb565(0x1f, 0x07, 0x07), // #1f0707 + rgb565(0x2f, 0x0f, 0x07), // #2f0f07 + rgb565(0x47, 0x0f, 0x07), // #470f07 + rgb565(0x57, 0x17, 0x07), // #571707 + rgb565(0x67, 0x1f, 0x07), // #671f07 + rgb565(0x77, 0x1f, 0x07), // #771f07 + rgb565(0x8f, 0x27, 0x07), // #8f2707 + rgb565(0x9f, 0x2f, 0x07), // #9f2f07 + rgb565(0xaf, 0x3f, 0x07), // #af3f07 + rgb565(0xbf, 0x47, 0x07), // #bf4707 + rgb565(0xc7, 0x47, 0x07), // #c74707 + rgb565(0xDF, 0x4F, 0x07), // #DF4F07 + rgb565(0xDF, 0x57, 0x07), // #DF5707 + rgb565(0xDF, 0x57, 0x07), // #DF5707 + rgb565(0xD7, 0x5F, 0x07), // #D75F07 + rgb565(0xD7, 0x67, 0x0F), // #D7670F + rgb565(0xcf, 0x6f, 0x0f), // #cf6f0f + rgb565(0xcf, 0x77, 0x0f), // #cf770f + rgb565(0xcf, 0x7f, 0x0f), // #cf7f0f + rgb565(0xCF, 0x87, 0x17), // #CF8717 + rgb565(0xC7, 0x87, 0x17), // #C78717 + rgb565(0xC7, 0x8F, 0x17), // #C78F17 + rgb565(0xC7, 0x97, 0x1F), // #C7971F + rgb565(0xBF, 0x9F, 0x1F), // #BF9F1F + rgb565(0xBF, 0x9F, 0x1F), // #BF9F1F + rgb565(0xBF, 0xA7, 0x27), // #BFA727 + rgb565(0xBF, 0xA7, 0x27), // #BFA727 + rgb565(0xBF, 0xAF, 0x2F), // #BFAF2F + rgb565(0xB7, 0xAF, 0x2F), // #B7AF2F + rgb565(0xB7, 0xB7, 0x2F), // #B7B72F + rgb565(0xB7, 0xB7, 0x37), // #B7B737 + rgb565(0xCF, 0xCF, 0x6F), // #CFCF6F + rgb565(0xDF, 0xDF, 0x9F), // #DFDF9F + rgb565(0xEF, 0xEF, 0xC7), // #EFEFC7 + rgb565(0xFF, 0xFF, 0xFF) // #FFFFFF +}; + +void setupFire(uint8_t** firePixels, uint16_t w, uint16_t h) { + for (uint8_t y = 0; y < h; y++) { + for (uint8_t x = 0; x < w; x++) { + // last row is hot + firePixels[y][x] = y == h - 1 ? 35 : 0; + } + } +} + +void calcFire(uint8_t** firePixels, uint16_t w, uint16_t h) { + for (uint8_t y = 0; y < h - 1; y++) { + for (uint8_t x = 0; x < w; x++) { + uint8_t wind = x + random(2); + wind = wind >= w ? wind - w : wind; + uint8_t speed = y + random(2); + speed = speed >= h ? h - 1 : speed; + firePixels[y][x] = firePixels[speed][wind] - random(2); + firePixels[y][x] = firePixels[y][x] > 35 ? 0 : firePixels[y][x]; // fix overflow + } + } +} + +void mapFire(uint8_t** firePixels, uint16_t fireW, uint16_t fireH, // + Graphics2D* graphics2d, uint16_t offsetX, uint16_t offsetY) { + for (uint8_t x = 0; x < fireW; x++) { + for (uint8_t y = 0; y < fireH; y++) { + graphics2d->drawPixel(x + offsetX, y + offsetY, doomColorMap[firePixels[y][x]]); + } + } +} diff --git a/src/animations/anim_firework.cpp b/src/animations/anim_firework.cpp new file mode 100644 index 000000000..19cde56c3 --- /dev/null +++ b/src/animations/anim_firework.cpp @@ -0,0 +1,61 @@ +#include "animations/anim_firework.h" + +#include "math_angles.h" +#include "gfx_util.h" +#include "gfx_2d.h" + +void Particle::tick(long ms, float friction, float gravity) { + // update position + locationX += speedX * (ms / 1000.0); + locationY += speedY * (ms / 1000.0); + + // update velocity + speedX = speedX - friction * (ms / 1000.0); + speedY = speedY + gravity * (ms / 1000.0); + // printf("particle at %d/%d\n", locationX, locationY); +} + +void Firework::init(uint16_t color_, uint8_t radius, uint8_t rings, // + uint16_t screenWidth, uint16_t screenHeight) { + height = 0; + explHeight = random((float)screenHeight * 0.2, (float)screenHeight * 0.8); + age = 0; + color = color_; + + for (uint8_t i = 0; i < numParticles; i++) { + // precalculate particle starting points + float pointsOnRing = ((float)numParticles / (float)rings); + uint8_t ring = (i / pointsOnRing) + 1; + float angle = (360.0 / pointsOnRing) * i; + + particles[i].locationX = rpx(0, ring * radius, angle); + particles[i].locationY = rpy(0, ring * radius, angle); + + // TODO: rotate particle velocities in a circle of radius + particles[i].speedX = rpx(0, radius, angle) * 2; + particles[i].speedY = rpy(0, radius, angle) * 2; + } +} + +void Firework::tick(long ms, uint8_t launchSpeed) { + if (height < explHeight) { + height += launchSpeed * (ms / 100.0); + } else { + for (uint8_t i = 0; i < numParticles; i++) { + particles[i].tick(ms, .1, 9.8); + } + + color = dimColor(color, age / 1000); + age += ms; + } +} + +void Firework::draw(Graphics2D* gfx, int16_t offsetX, int16_t offsetY) { + if (height < explHeight) { + gfx->drawPixel(offsetX, offsetY - height, rgb565(255, 255, 255)); + } else { + for (uint8_t i = 0; i < numParticles; i++) { + gfx->drawPixel(offsetX + (int)particles[i].locationX, offsetY - height + (int)particles[i].locationY, color); + } + } +} \ No newline at end of file diff --git a/src/animations/anim_water_ripple.cpp b/src/animations/anim_water_ripple.cpp new file mode 100644 index 000000000..115e2280f --- /dev/null +++ b/src/animations/anim_water_ripple.cpp @@ -0,0 +1,46 @@ +#include "animations/anim_water_ripple.h" + +#include + +#include "gfx_2d.h" +#include "gfx_util.h" + +// helper to calculate position in the buffer +#define a(x, y, width) (x) + ((y)*width) + +// implementation according to: +// https://web.archive.org/web/20160418004149/http://freespace.virgin.net/hugo.elias/graphics/x_water.htm + +void calcWater(int8_t* buf1, int8_t* buf2, uint16_t width, uint16_t height, float damping /* [0,1]*/) { + // for every non-edge element + + // this could be faster, but this is easier to understand: + for (uint16_t x = 1; x < width - 1; x++) { + for (uint16_t y = 1; y < height - 1; y++) { + int16_t velocity = -buf2[a(x, y, width)]; + int16_t smoothed = (buf1[a(x - 1, y, width)] // + + buf1[a(x + 1, y, width)] // + + buf1[a(x, y + 1, width)] // + + buf1[a(x, y - 1, width)]) / + 2; + buf2[a(x, y, width)] = (smoothed + velocity) * damping; + } + } +} + +void mapWater(int8_t* buf, uint16_t width, uint16_t height, Graphics2D* background, Graphics2D* target, + uint16_t offsetX, uint16_t offsetY) { + for (uint16_t x = 1; x < width - 1; x++) { + for (uint16_t y = 1; y < height - 1; y++) { + int16_t xShift = x + (buf[a(x - 1, y, width)] - buf[a(x + 1, y, width)]) / 2; + int16_t yShift = y + (buf[a(x, y - 1, width)] - buf[a(x, y + 1, width)]) / 2; + + xShift = xShift >= width ? width - 1: xShift; + xShift = xShift < 0 ? 0 : xShift; + yShift = yShift >= height ? height - 1: yShift; + yShift = yShift < 0 ? 0 : yShift; + int16_t shiftedColor = background->getPixel(xShift, yShift); + target->drawPixel(x, y, shiftedColor); + } + } +} diff --git a/src/animations/demos/README.md b/src/animations/demos/README.md new file mode 100644 index 000000000..bad304507 --- /dev/null +++ b/src/animations/demos/README.md @@ -0,0 +1,4 @@ +This folder holds now inactive (by changed extensions) and broken code to demonstrate these animations. +These snippets were kept, as not every animation is just simply useable (...). + +Feel free to explore them and maybe even polish them up, so they work with the emulator again... \ No newline at end of file diff --git a/src/animations/demos/doom-fire-old.cpp.inactive b/src/animations/demos/doom-fire-old.cpp.inactive new file mode 100644 index 000000000..48f1da72a --- /dev/null +++ b/src/animations/demos/doom-fire-old.cpp.inactive @@ -0,0 +1,39 @@ + +#include + +#include + +#include "../../FakeArduino.h" +#include "../../FakeArduinoWindowSDL.h" +#include "../../anim_doom_fire_old.h" +#include "../../gfx_util.h" +using namespace std; + +#define BUF_W 240 +#define BUF_H 240 +uint8_t** firePixels = new uint8_t*[BUF_H]; +Graphics2D gfx2d(BUF_W, BUF_H,120); + +class FireWindow : public SDLWindowRGB565 { + public: + FireWindow(Graphics2D* gfx2d, int w, int h) : SDLWindowRGB565(gfx2d, w, h) {} + void setup() { + // setup array rows + for (int i = 0; i < BUF_H; i++) { + firePixels[i] = new uint8_t[BUF_W]; + } + setupFire(firePixels, BUF_W, BUF_H); + } + + void loop() { + delay(1000 / 30); + calcFire(firePixels, BUF_W, BUF_H); + mapFire(firePixels, BUF_W, BUF_H, &gfx2d, 0, 0); + } +}; + +int main(int argc, char* argv[]) { + FireWindow fw(&gfx2d, BUF_W, BUF_H); + fw.run(); + return 0; +} diff --git a/src/animations/demos/doom-fire.cpp.inactive b/src/animations/demos/doom-fire.cpp.inactive new file mode 100644 index 000000000..fa540ba4e --- /dev/null +++ b/src/animations/demos/doom-fire.cpp.inactive @@ -0,0 +1,40 @@ + +#include + +#include + +#include "../../FakeArduino.h" +#include "../../FakeArduinoWindowSDL.h" +#include "../../anim_doom_fire.h" +#include "../../gfx_util.h" +using namespace std; + +#define BUF_W 240 +#define BUF_H 240 + +Graphics2DPrint gfx2d(BUF_W, BUF_H, 120); +AnimDoomFire d; +class FireWindow : public SDLWindowRGB565 { + public: + FireWindow(Graphics2D* gfx2d, int w, int h) : SDLWindowRGB565(gfx2d, w, h) {} + void setup() { + + } + + void loop() { + Graphics2DPrint* gfx = &gfx2d; + + gfx->fill(rgb565(0, 0, 0)); + + // render animation + d.loop(gfx); + + delay(1000 / 30); + } +}; + +int main(int argc, char* argv[]) { + FireWindow fw(&gfx2d, BUF_W, BUF_H); + fw.run(); + return 0; +} diff --git a/src/animations/demos/fireworks.cpp.inactive b/src/animations/demos/fireworks.cpp.inactive new file mode 100644 index 000000000..fd11d5683 --- /dev/null +++ b/src/animations/demos/fireworks.cpp.inactive @@ -0,0 +1,59 @@ +#include + +#include + +#include "../../FakeArduino.h" +#include "../../FakeArduinoWindowSDL.h" +#include "../../anim_firework.h" +#include "../../gfx_2d.h" +#include "../../gfx_util.h" +#include "../../math_angles.h" +using namespace std; + +#define BUF_W 240 +#define BUF_H 240 + +#define NUM_FIREWORKS 10 +Graphics2D gfx2d(BUF_W, BUF_H, 4, false); +Firework fireworks[NUM_FIREWORKS]; +int16_t offsets[NUM_FIREWORKS]; + +class WatchSimpleWindow : public SDLWindowRGB565 { + public: + WatchSimpleWindow(Graphics2D* gfx2d, int w, int h) : SDLWindowRGB565(gfx2d, w, h) {} + void setup() { + // draw background black + gfx2d.fill(rgb565(0, 0, 0)); + for (uint16_t i = 0; i < NUM_FIREWORKS; i++) { + offsets[i] = random(40, 200); + } + } + + void loop() { + static long lastTick = millis(); + + for (uint16_t i = 0; i < NUM_FIREWORKS; i++) { + fireworks[i].draw(&gfx2d, offsets[i], BUF_H); + fireworks[i].tick(millis() - lastTick, 10); + + if (fireworks[i].age > random(3000, 12000)) { + uint16_t color = rgb565(random(100, 255), random(100, 255), random(100, 255)); + uint8_t radius = random(1, 8); + uint8_t rings = random(3, 6); + offsets[i] = random(40, 200); + fireworks[i].init(color, radius, rings, BUF_W, BUF_H); + } + } + + gfx2d.dim(10); + + lastTick = millis(); + delay(1000 / 30); + } +}; + +int main(int argc, char* argv[]) { + WatchSimpleWindow wsw(&gfx2d, BUF_W, BUF_H); + wsw.run(); + return 0; +} diff --git a/src/animations/demos/matrix.cpp.inactive b/src/animations/demos/matrix.cpp.inactive new file mode 100644 index 000000000..d9e7536e0 --- /dev/null +++ b/src/animations/demos/matrix.cpp.inactive @@ -0,0 +1,44 @@ +#include +#include + +#include "../../FakeArduino.h" +#include "../../FakeArduinoWindowSDL.h" +#include "../../anim_matrix.h" +#include "../../anim_water_ripple.h" +#include "../../gfx_2d_print.h" +#include "../../gfx_util.h" + +using namespace std; + +#define BUF_W 240 +#define BUF_H 240 + +Graphics2DPrint gfx2d(BUF_W, BUF_H, 16); + +// create animation object +AnimMatrix m(&gfx2d); + +class RotationExampleWindow : public SDLWindowRGB565 { + public: + RotationExampleWindow(Graphics2D* gfx2d, int w, int h) : SDLWindowRGB565(gfx2d, w, h) {} + void setup() {} + + void loop() { + static uint16_t counter = 1; + Graphics2DPrint* gfx = &gfx2d; + + counter++; + gfx->fill(rgb565(0, 0, 0)); + + // render animation + m.loop(gfx); + + delay(1000 / 30); + } +}; + +int main(int argc, char* argv[]) { + RotationExampleWindow exampleWindow(&gfx2d, BUF_W, BUF_H); + exampleWindow.run(); + return 0; +} diff --git a/src/animations/demos/mix-face.cpp.inactive b/src/animations/demos/mix-face.cpp.inactive new file mode 100644 index 000000000..ed2e832e4 --- /dev/null +++ b/src/animations/demos/mix-face.cpp.inactive @@ -0,0 +1,169 @@ +#include + +#include + +#include "../../FakeArduino.h" +#include "../../FakeArduinoWindowSDL.h" +#include "../../gfx_2d.h" + +#include "../../gfx_2d_print.h" +#include "../../gfx_util.h" + +#include "../../fonts/Picopixel.h" +#include "../../fonts/FreeSans11pt8b.h" +#include "../../math_angles.h" + +using namespace std; + +#define BUF_W 240 +#define BUF_H 240 +uint16_t screenBuffer[BUF_W * BUF_H]; + +Graphics2DPrint gfx2d_print(BUF_W, BUF_H, 16); + +class MixFace : public SDLWindowRGB565 { + public: + MixFace(Graphics2DPrint* gfx2d_print, int w, int h) : SDLWindowRGB565(gfx2d_print, w, h) {} + void setup() {} + + void loop() { + delay(1000 / 30); // 30FPS + static uint x = 0; + x++; + + uint8_t cx = 65; + uint8_t cy = 100; + uint8_t control_size = 2; + uint8_t alp = 3; + + char am[] = "AM"; + char pm[] = "PM"; + + gfx2d_print.fill(rgb565(1, 1, 1)); + + // Left Analog Watch + + // 3rd and 4th param - + // Decrease as you get closer to " 0 ". + // The larger the difference in values, the longer it is. + gfx2d_print.drawHourTicks(cx, cy, 45 , 40 , rgb565(255, 255, 255)); + gfx2d_print.drawCircle(cx, cy,50 , rgb565(255, 255, 255)); + + // hour + gfx2d_print.drawLine(cx, cy, rpx(cx, 33 / control_size, h2d(x)), rpy(cy, 33 / control_size, h2d(x)), rgb565(255, 255, 255)); + // // minute + gfx2d_print.drawLine(cx, cy, rpx(cx, 66 / control_size, m2d(x)), rpy(cy, 66 / control_size, m2d(x)), rgb565(0, 255, 0)); + // // second + gfx2d_print.drawLine(cx, cy, rpx(cx, 15 / control_size, s2d(x) + 180), rpy(cy, 15 / control_size, s2d(x) + 180), rgb565(255, 0, 0)); // short backwards + gfx2d_print.drawLine(cx, cy, rpx(cx, 90 / control_size, s2d(x)), rpy(cy, 90 / control_size, s2d(x)), rgb565(255, 0, 0)); // long front + + // Right Digital Watch + gfx2d_print.setTextSize(1); + gfx2d_print.setTextMiddleAligned(); + gfx2d_print.setTextLeftAligned(); + gfx2d_print.setTextCursor(120 +alp, 75); + const char* weekday = "Sun"; + { + char weekday3[4]; + + switch((int)(x*0.05)%7){ + case 0: + weekday3[0] = 'S'; + weekday3[1] = 'u'; + weekday3[2] = 'n'; + break; + case 1: + weekday3[0] = 'M'; + weekday3[1] = 'o'; + weekday3[2] = 'n'; + break; + case 2: + weekday3[0] = 'T'; + weekday3[1] = 'u'; + weekday3[2] = 'e'; + break; + case 3: + weekday3[0] = 'W'; + weekday3[1] = 'e'; + weekday3[2] = 'd'; + break; + case 4: + weekday3[0] = 'T'; + weekday3[1] = 'h'; + weekday3[2] = 'r'; + break; + case 5: + weekday3[0] = 'F'; + weekday3[1] = 'r'; + weekday3[2] = 'i'; + break; + case 6: + weekday3[0] = 'S'; + weekday3[1] = 'a'; + weekday3[2] = 't'; + break; + } + weekday3[3] = '\0'; + gfx2d_print.print(weekday3); + } + ////date + gfx2d_print.setTextSize(2); + gfx2d_print.setTextMiddleAligned(); + gfx2d_print.setTextLeftAligned(); + gfx2d_print.setTextCursor(120 + alp, 90); + + gfx2d_print.printDecimal((int)(x*0.2)%31+1, 2); + gfx2d_print.print("."); + gfx2d_print.printDecimal((int)(x*0.6)% 12+1, 2); + gfx2d_print.print("."); + gfx2d_print.printDecimal((int)(x*0.9)%100,2); // Full year-name is overflowing + + ////time + gfx2d_print.setTextSize(3); + gfx2d_print.setTextMiddleAligned(); + gfx2d_print.setTextLeftAligned(); + gfx2d_print.setTextCursor(120 + alp, 120); + + gfx2d_print.printDecimal((int)(x * 0.4) % 12, 2); + gfx2d_print.print(":"); + gfx2d_print.printDecimal((int)(x * 0.8) % 60, 2); + + gfx2d_print.setTextSize(1); + gfx2d_print.setTextMiddleAligned(); + gfx2d_print.setTextLeftAligned(); + gfx2d_print.setTextBottomAligned(); + gfx2d_print.setTextCursor(120 + 100, 120+10); + if (x % 60>=30) { + gfx2d_print.print(pm); + } else { + gfx2d_print.print(am); + } + + // test - step counter just frame + for (uint8_t i = 0; i < 7; i++) { + uint32_t s = x * (i + 1) * 100 % 10000; // virtual step simulation + uint16_t boxHeight = ((float)(s > 10000 ? 10000 : s) / 10000) * 32; + boxHeight = boxHeight < 2 ? 0 : boxHeight; + + // step bars + gfx2d_print.fillFrame(((240 / 2) - 8 * 3.5) + i * 8, 180 + (32 - boxHeight), 8, boxHeight, rgb888to565(rgb888(32, 156, 238))); + + gfx2d_print.drawRFrame(((240 / 2) - 8 * 3.5) + i * 8, 180, 8, 32, 2, rgb888to565(rgb888(122, 122, 122))); + } + + // labels + gfx2d_print.setTextCenterAligned(); // horiz. + gfx2d_print.setTextBottomAligned(); + gfx2d_print.setTextSize(1); + gfx2d_print.setTextCursor(240 / 2, 180 - 1); + gfx2d_print.print((int)(x * 0.04)); // today step counter + gfx2d_print.setTextCursor(240 / 2, 180 + 1 + 8 + 8 * 4); + gfx2d_print.print(x); // total step counter + } +}; + +int main(int argc, char* argv[]) { + MixFace mf(&gfx2d_print,BUF_W, BUF_H); + mf.run(); + return 0; +} diff --git a/src/animations/demos/osm.cpp.inactive b/src/animations/demos/osm.cpp.inactive new file mode 100644 index 000000000..8cbfbb55a --- /dev/null +++ b/src/animations/demos/osm.cpp.inactive @@ -0,0 +1,85 @@ +#include +#include + +#include "../../FakeArduino.h" +#include "../../FakeArduinoWindowSDL.h" +#include "../../gfx_2d.h" +#include "../../gfx_util.h" +#include "../../osm_render.h" + +// full screen buffer +#define BUF_W 240 +#define BUF_H 240 + +// we buffer a quarter tile + 50% and upscale it by a factor of 2x +// this way we greatly reduce the number of reads from the uSD to load new tiles +// when moving around, and reduce memory consumption +#define MAP_BUF_W 255 +#define MAP_BUF_H 255 + +Graphics2D gfx2d(BUF_W, BUF_H, 16); + +Graphics2D tileBuffer(MAP_BUF_W, MAP_BUF_H, 5); + +void loadTileFn(Graphics2D* target, int8_t z, float tilex, float tiley, int32_t offsetx, int32_t offsety) { + int16_t xStart = max(0, offsetx); + int16_t xEnd = min(target->getWidth(), target->getWidth() + offsetx); + int16_t yStart = max(0, offsety); + int16_t yEnd = min(target->getHeight(), target->getHeight() + offsety); + // printf("xStart %d, xEnd %d, yStart %d, yEnd %d", xStart, xEnd, yStart, yEnd); + uint8_t r = 0; + uint8_t g = 0; + uint8_t b = 0; + + Graphics2D* tile = new Graphics2D(256, 256, 16); + const char* path = ("/Volumes/TILEDISK/map/" + std::to_string(z) + "/" + std::to_string((int)tilex) + "/" + + std::to_string((int)tiley) + ".png") + .c_str(); + loadPNG(tile, path); + + for (int16_t x = xStart; x < xEnd; x++) { + for (int16_t y = yStart; y < yEnd; y++) { + r++; + g++; + uint16_t color = rgb565(r, g, b); + // TODO: optimize for offset + target->drawPixel(x, y, tile->getPixel(x - offsetx, y - offsety)); + } + b++; + } +} + +float lat = 50.76; +float lon = 4.21; +uint16_t z = 10; + +class RotationExampleWindow : public SDLWindowRGB565 { + public: + RotationExampleWindow(Graphics2D* gfx2d, int w, int h) : SDLWindowRGB565(gfx2d, w, h) {} + void setup() {} + + void loop() { + static uint16_t counter = 1; + counter++; + + drawTiles(&tileBuffer, (loadTile)loadTileFn, lat, lon, z); + gfx2d.drawGraphics2D(0, 0, &tileBuffer); + + delay(1000 / 30); + lat += 0.001; + lon += 0.001; + + // if (counter % 100 == 0) { + // z++; + // if (z == 17) { + // z = 6; + // } + // } + } +}; + +int main(int argc, char* argv[]) { + RotationExampleWindow exampleWindow(&gfx2d, BUF_W, BUF_H); + exampleWindow.run(); + return 0; +} diff --git a/src/animations/demos/perlin/SimplexNoise.cpp.inactive b/src/animations/demos/perlin/SimplexNoise.cpp.inactive new file mode 100644 index 000000000..9a6df68ca --- /dev/null +++ b/src/animations/demos/perlin/SimplexNoise.cpp.inactive @@ -0,0 +1,475 @@ +/** + * @file SimplexNoise.cpp + * @brief A Perlin Simplex Noise C++ Implementation (1D, 2D, 3D). + * + * Copyright (c) 2014-2018 Sebastien Rombauts (sebastien.rombauts@gmail.com) + * + * This C++ implementation is based on the speed-improved Java version 2012-03-09 + * by Stefan Gustavson (original Java source code in the public domain). + * http://webstaff.itn.liu.se/~stegu/simplexnoise/SimplexNoise.java: + * - Based on example code by Stefan Gustavson (stegu@itn.liu.se). + * - Optimisations by Peter Eastman (peastman@drizzle.stanford.edu). + * - Better rank ordering method by Stefan Gustavson in 2012. + * + * This implementation is "Simplex Noise" as presented by + * Ken Perlin at a relatively obscure and not often cited course + * session "Real-Time Shading" at Siggraph 2001 (before real + * time shading actually took on), under the title "hardware noise". + * The 3D function is numerically equivalent to his Java reference + * code available in the PDF course notes, although I re-implemented + * it from scratch to get more readable code. The 1D, 2D and 4D cases + * were implemented from scratch by me from Ken Perlin's text. + * + * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt + * or copy at http://opensource.org/licenses/MIT) + */ + +#include "SimplexNoise.h" + +#include // int32_t/uint8_t + +/** + * Computes the largest integer value not greater than the float one + * + * This method is faster than using (int32_t)std::floor(fp). + * + * I measured it to be approximately twice as fast: + * float: ~18.4ns instead of ~39.6ns on an AMD APU), + * double: ~20.6ns instead of ~36.6ns on an AMD APU), + * Reference: http://www.codeproject.com/Tips/700780/Fast-floor-ceiling-functions + * + * @param[in] fp float input value + * + * @return largest integer value not greater than fp + */ +static inline int32_t fastfloor(float fp) { + int32_t i = static_cast(fp); + return (fp < i) ? (i - 1) : (i); +} + +/** + * Permutation table. This is just a random jumble of all numbers 0-255. + * + * This produce a repeatable pattern of 256, but Ken Perlin stated + * that it is not a problem for graphic texture as the noise features disappear + * at a distance far enough to be able to see a repeatable pattern of 256. + * + * This needs to be exactly the same for all instances on all platforms, + * so it's easiest to just keep it as static explicit data. + * This also removes the need for any initialisation of this class. + * + * Note that making this an uint32_t[] instead of a uint8_t[] might make the + * code run faster on platforms with a high penalty for unaligned single + * byte addressing. Intel x86 is generally single-byte-friendly, but + * some other CPUs are faster with 4-aligned reads. + * However, a char[] is smaller, which avoids cache trashing, and that + * is probably the most important aspect on most architectures. + * This array is accessed a *lot* by the noise functions. + * A vector-valued noise over 3D accesses it 96 times, and a + * float-valued 4D noise 64 times. We want this to fit in the cache! + */ +static const uint8_t perm[256] = { + 151, 160, 137, 91, 90, 15, + 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, + 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32, 57, 177, 33, + 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, + 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, + 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, + 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, 226, 250, 124, 123, + 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, + 223, 183, 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, + 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, + 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, 249, 14, 239, 107, + 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, + 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 +}; + +/** + * Helper function to hash an integer using the above permutation table + * + * This inline function costs around 1ns, and is called N+1 times for a noise of N dimension. + * + * Using a real hash function would be better to improve the "repeatability of 256" of the above permutation table, + * but fast integer Hash functions uses more time and have bad random properties. + * + * @param[in] i Integer value to hash + * + * @return 8-bits hashed value + */ +static inline uint8_t hash(int32_t i) { + return perm[static_cast(i)]; +} + +/* NOTE Gradient table to test if lookup-table are more efficient than calculs +static const float gradients1D[16] = { + -8.f, -7.f, -6.f, -5.f, -4.f, -3.f, -2.f, -1.f, + 1.f, 2.f, 3.f, 4.f, 5.f, 6.f, 7.f, 8.f +}; +*/ + +/** + * Helper function to compute gradients-dot-residual vectors (1D) + * + * @note that these generate gradients of more than unit length. To make + * a close match with the value range of classic Perlin noise, the final + * noise values need to be rescaled to fit nicely within [-1,1]. + * (The simplex noise functions as such also have different scaling.) + * Note also that these noise functions are the most practical and useful + * signed version of Perlin noise. + * + * @param[in] hash hash value + * @param[in] x distance to the corner + * + * @return gradient value + */ +static float grad(int32_t hash, float x) { + const int32_t h = hash & 0x0F; // Convert low 4 bits of hash code + float grad = 1.0f + (h & 7); // Gradient value 1.0, 2.0, ..., 8.0 + if ((h & 8) != 0) grad = -grad; // Set a random sign for the gradient +// float grad = gradients1D[h]; // NOTE : Test of Gradient look-up table instead of the above + return (grad * x); // Multiply the gradient with the distance +} + +/** + * Helper functions to compute gradients-dot-residual vectors (2D) + * + * @param[in] hash hash value + * @param[in] x x coord of the distance to the corner + * @param[in] y y coord of the distance to the corner + * + * @return gradient value + */ +static float grad(int32_t hash, float x, float y) { + const int32_t h = hash & 0x3F; // Convert low 3 bits of hash code + const float u = h < 4 ? x : y; // into 8 simple gradient directions, + const float v = h < 4 ? y : x; + return ((h & 1) ? -u : u) + ((h & 2) ? -2.0f * v : 2.0f * v); // and compute the dot product with (x,y). +} + +/** + * Helper functions to compute gradients-dot-residual vectors (3D) + * + * @param[in] hash hash value + * @param[in] x x coord of the distance to the corner + * @param[in] y y coord of the distance to the corner + * @param[in] z z coord of the distance to the corner + * + * @return gradient value + */ +static float grad(int32_t hash, float x, float y, float z) { + int h = hash & 15; // Convert low 4 bits of hash code into 12 simple + float u = h < 8 ? x : y; // gradient directions, and compute dot product. + float v = h < 4 ? y : h == 12 || h == 14 ? x : z; // Fix repeats at h = 12 to 15 + return ((h & 1) ? -u : u) + ((h & 2) ? -v : v); +} + +/** + * 1D Perlin simplex noise + * + * Takes around 74ns on an AMD APU. + * + * @param[in] x float coordinate + * + * @return Noise value in the range[-1; 1], value of 0 on all integer coordinates. + */ +float SimplexNoise::noise(float x) { + float n0, n1; // Noise contributions from the two "corners" + + // No need to skew the input space in 1D + + // Corners coordinates (nearest integer values): + int32_t i0 = fastfloor(x); + int32_t i1 = i0 + 1; + // Distances to corners (between 0 and 1): + float x0 = x - i0; + float x1 = x0 - 1.0f; + + // Calculate the contribution from the first corner + float t0 = 1.0f - x0*x0; +// if(t0 < 0.0f) t0 = 0.0f; // not possible + t0 *= t0; + n0 = t0 * t0 * grad(hash(i0), x0); + + // Calculate the contribution from the second corner + float t1 = 1.0f - x1*x1; +// if(t1 < 0.0f) t1 = 0.0f; // not possible + t1 *= t1; + n1 = t1 * t1 * grad(hash(i1), x1); + + // The maximum value of this noise is 8*(3/4)^4 = 2.53125 + // A factor of 0.395 scales to fit exactly within [-1,1] + return 0.395f * (n0 + n1); +} + +/** + * 2D Perlin simplex noise + * + * Takes around 150ns on an AMD APU. + * + * @param[in] x float coordinate + * @param[in] y float coordinate + * + * @return Noise value in the range[-1; 1], value of 0 on all integer coordinates. + */ +float SimplexNoise::noise(float x, float y) { + float n0, n1, n2; // Noise contributions from the three corners + + // Skewing/Unskewing factors for 2D + static const float F2 = 0.366025403f; // F2 = (sqrt(3) - 1) / 2 + static const float G2 = 0.211324865f; // G2 = (3 - sqrt(3)) / 6 = F2 / (1 + 2 * K) + + // Skew the input space to determine which simplex cell we're in + const float s = (x + y) * F2; // Hairy factor for 2D + const float xs = x + s; + const float ys = y + s; + const int32_t i = fastfloor(xs); + const int32_t j = fastfloor(ys); + + // Unskew the cell origin back to (x,y) space + const float t = static_cast(i + j) * G2; + const float X0 = i - t; + const float Y0 = j - t; + const float x0 = x - X0; // The x,y distances from the cell origin + const float y0 = y - Y0; + + // For the 2D case, the simplex shape is an equilateral triangle. + // Determine which simplex we are in. + int32_t i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords + if (x0 > y0) { // lower triangle, XY order: (0,0)->(1,0)->(1,1) + i1 = 1; + j1 = 0; + } else { // upper triangle, YX order: (0,0)->(0,1)->(1,1) + i1 = 0; + j1 = 1; + } + + // A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and + // a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where + // c = (3-sqrt(3))/6 + + const float x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords + const float y1 = y0 - j1 + G2; + const float x2 = x0 - 1.0f + 2.0f * G2; // Offsets for last corner in (x,y) unskewed coords + const float y2 = y0 - 1.0f + 2.0f * G2; + + // Work out the hashed gradient indices of the three simplex corners + const int gi0 = hash(i + hash(j)); + const int gi1 = hash(i + i1 + hash(j + j1)); + const int gi2 = hash(i + 1 + hash(j + 1)); + + // Calculate the contribution from the first corner + float t0 = 0.5f - x0*x0 - y0*y0; + if (t0 < 0.0f) { + n0 = 0.0f; + } else { + t0 *= t0; + n0 = t0 * t0 * grad(gi0, x0, y0); + } + + // Calculate the contribution from the second corner + float t1 = 0.5f - x1*x1 - y1*y1; + if (t1 < 0.0f) { + n1 = 0.0f; + } else { + t1 *= t1; + n1 = t1 * t1 * grad(gi1, x1, y1); + } + + // Calculate the contribution from the third corner + float t2 = 0.5f - x2*x2 - y2*y2; + if (t2 < 0.0f) { + n2 = 0.0f; + } else { + t2 *= t2; + n2 = t2 * t2 * grad(gi2, x2, y2); + } + + // Add contributions from each corner to get the final noise value. + // The result is scaled to return values in the interval [-1,1]. + return 45.23065f * (n0 + n1 + n2); +} + + +/** + * 3D Perlin simplex noise + * + * @param[in] x float coordinate + * @param[in] y float coordinate + * @param[in] z float coordinate + * + * @return Noise value in the range[-1; 1], value of 0 on all integer coordinates. + */ +float SimplexNoise::noise(float x, float y, float z) { + float n0, n1, n2, n3; // Noise contributions from the four corners + + // Skewing/Unskewing factors for 3D + static const float F3 = 1.0f / 3.0f; + static const float G3 = 1.0f / 6.0f; + + // Skew the input space to determine which simplex cell we're in + float s = (x + y + z) * F3; // Very nice and simple skew factor for 3D + int i = fastfloor(x + s); + int j = fastfloor(y + s); + int k = fastfloor(z + s); + float t = (i + j + k) * G3; + float X0 = i - t; // Unskew the cell origin back to (x,y,z) space + float Y0 = j - t; + float Z0 = k - t; + float x0 = x - X0; // The x,y,z distances from the cell origin + float y0 = y - Y0; + float z0 = z - Z0; + + // For the 3D case, the simplex shape is a slightly irregular tetrahedron. + // Determine which simplex we are in. + int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords + int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords + if (x0 >= y0) { + if (y0 >= z0) { + i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 1; k2 = 0; // X Y Z order + } else if (x0 >= z0) { + i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 0; k2 = 1; // X Z Y order + } else { + i1 = 0; j1 = 0; k1 = 1; i2 = 1; j2 = 0; k2 = 1; // Z X Y order + } + } else { // x0 // size_t + +/** + * @brief A Perlin Simplex Noise C++ Implementation (1D, 2D, 3D, 4D). + */ +class SimplexNoise { +public: + // 1D Perlin simplex noise + static float noise(float x); + // 2D Perlin simplex noise + static float noise(float x, float y); + // 3D Perlin simplex noise + static float noise(float x, float y, float z); + + // Fractal/Fractional Brownian Motion (fBm) noise summation + float fractal(size_t octaves, float x) const; + float fractal(size_t octaves, float x, float y) const; + float fractal(size_t octaves, float x, float y, float z) const; + + /** + * Constructor of to initialize a fractal noise summation + * + * @param[in] frequency Frequency ("width") of the first octave of noise (default to 1.0) + * @param[in] amplitude Amplitude ("height") of the first octave of noise (default to 1.0) + * @param[in] lacunarity Lacunarity specifies the frequency multiplier between successive octaves (default to 2.0). + * @param[in] persistence Persistence is the loss of amplitude between successive octaves (usually 1/lacunarity) + */ + explicit SimplexNoise(float frequency = 1.0f, + float amplitude = 1.0f, + float lacunarity = 2.0f, + float persistence = 0.5f) : + mFrequency(frequency), + mAmplitude(amplitude), + mLacunarity(lacunarity), + mPersistence(persistence) { + } + +private: + // Parameters of Fractional Brownian Motion (fBm) : sum of N "octaves" of noise + float mFrequency; ///< Frequency ("width") of the first octave of noise (default to 1.0) + float mAmplitude; ///< Amplitude ("height") of the first octave of noise (default to 1.0) + float mLacunarity; ///< Lacunarity specifies the frequency multiplier between successive octaves (default to 2.0). + float mPersistence; ///< Persistence is the loss of amplitude between successive octaves (usually 1/lacunarity) +}; \ No newline at end of file diff --git a/src/animations/demos/perlin/SimplexNosie.License.txt b/src/animations/demos/perlin/SimplexNosie.License.txt new file mode 100644 index 000000000..f69ae4096 --- /dev/null +++ b/src/animations/demos/perlin/SimplexNosie.License.txt @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2012-2018 Sebastien Rombauts (sebastien.rombauts@gmail.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/src/animations/demos/perlin/main.cpp.inactive b/src/animations/demos/perlin/main.cpp.inactive new file mode 100644 index 000000000..afc121e6b --- /dev/null +++ b/src/animations/demos/perlin/main.cpp.inactive @@ -0,0 +1,73 @@ + +#include + +#include + +#include "../../FakeArduino.h" +#include "../../FakeArduinoWindowSDL.h" +#include "../../gfx_2d.h" +#include "../../gfx_util.h" +#include "../../math_angles.h" +#include "../../SimplexNoise.h" + +SimplexNoise sn; + +using namespace std; + +#define BUF_W 240 +#define BUF_H 240 +uint16_t screenBuffer[BUF_W * BUF_H]; + +Graphics2D gfx2d(BUF_W, BUF_H, 16); + +// mix colors +int16_t mix(uint8_t base, float n1, float n2) { return (n1 - abs(n2)) * base; } +void fillPerlin(uint16_t counter) { + for (uint8_t x = 0; x < BUF_W; x++) { + for (uint8_t y = 0; y < BUF_H; y++) { + float n1 = sn.fractal(7, (x + counter) / 120.0, y / 120.0); // sn.noise returns [-1,1] + float n2 = sn.noise((x + counter) / 2, y / 2); // sn.noise returns [-1,1] + + uint8_t r = n1 > 0 ? (n1 - n2) * 255 : 0; + uint8_t g = r; + uint8_t b = r; + + uint16_t color = rgb565(r, g, b); + + if (n1 < -.1) { // deep water + color = rgb565(72 * (1 + n1) + mix(5, n1, n2), 72 * (1 + n1) + mix(5, n1, n2), 190 * (1 + n1) + mix(5, n1, n2)); + } else if (n1 < 0) { // shallow water plateaus + color = rgb565(72 + mix(5, n1, n2), 72 + mix(5, n1, n2), 190 + mix(5, n1, n2)); + } else if (n1 < .0125) { // beaches + color = rgb565(200 + mix(5, n1, n2), 200 + mix(20, n1, n2), 47 + mix(5, n1, n2)); + } else if (n1 < .2) { // meadows + color = rgb565(85 + mix(40, n1, n2), 107 + mix(20, n1, n2), 47 + mix(20, n1, n2)); + } else if (n1 < .7) { // forest + color = rgb565((85 * (1 - (n1 - .2) * 2)) + (35 * ((n1 - .2) * 2)) - mix(10, n1, n2), + (107 * (1 - (n1 - .2) * 2)) + (57 * ((n1 - .2) * 2)) - mix(20, n1, n2), + (47 * (1 - (n1 - .2) * 2)) + (7 * ((n1 - .2) * 2)) - mix(10, n1, n2)); + } else { + color = rgb565(35, 57, 7) - rgb565(85 + mix(40, n1, n2), 107 + mix(20, n1, n2), 47 + mix(20, n1, n2)); + } + + gfx2d.drawPixel(x, y, color); + } + } +} +class PerlinWindow : public SDLWindowRGB565 { + public: + PerlinWindow(Graphics2D* gfx2d, int w, int h) : SDLWindowRGB565(gfx2d, w, h) {} + void setup() { printf("zero is %d", rgb565(0, 0, 0)); } + + void loop() { + static uint16_t counter = 0; + counter++; + fillPerlin(counter); + } +}; + +int main(int argc, char* argv[]) { + PerlinWindow pw(&gfx2d, BUF_W, BUF_H); + pw.run(); + return 0; +} diff --git a/src/animations/demos/rotation/leafs_128_32_4x.png b/src/animations/demos/rotation/leafs_128_32_4x.png new file mode 100644 index 000000000..7f010bd1f Binary files /dev/null and b/src/animations/demos/rotation/leafs_128_32_4x.png differ diff --git a/src/animations/demos/rotation/main.cpp.inactive b/src/animations/demos/rotation/main.cpp.inactive new file mode 100644 index 000000000..3e94c9c72 --- /dev/null +++ b/src/animations/demos/rotation/main.cpp.inactive @@ -0,0 +1,113 @@ +#include +#include + +#include "../../FakeArduino.h" +#include "../../FakeArduinoWindowSDL.h" +#include "../../anim_water_ripple.h" +#include "../../gfx_2d.h" +#include "../../gfx_util.h" + +using namespace std; + +#define BUF_W 240 +#define BUF_H 240 + +#define WATER_W 120 +#define WATER_H 120 + +Graphics2D gfx2d(BUF_W, BUF_H, 16); + +Graphics2D waterBackground(WATER_W, WATER_H, 8); +Graphics2D waterScreenBuffer(WATER_W, WATER_H, 8); + +Graphics2D sprites(128, 32, 32); + +Graphics2D leaf0(32, 32, 32); +Graphics2D leaf1(32, 32, 32); +Graphics2D leaf2(32, 32, 32); +Graphics2D leaf3(32, 32, 32); + +int8_t wbuf1[WATER_W * WATER_H]; +int8_t wbuf2[WATER_W * WATER_H]; + +uint16_t maskColor = rgb565(0, 0, 0); + +uint8_t lx1 = 60 + (-20 + random(40)), ly1 = 60 + (-30 + random(60)); +uint8_t lx2 = 60 + (-20 + random(40)), ly2 = 60 + (-30 + random(60)); +uint8_t lx3 = 60 + (-20 + random(40)), ly3 = 60 + (-30 + random(60)); +uint8_t lx4 = 60 + (-20 + random(40)), ly4 = 60 + (-30 + random(60)); + +int8_t mx1 = -2 + random(10), my1 = -2 + random(10); +int8_t mx2 = -2 + random(10), my2 = -2 + random(10); +int8_t mx3 = -2 + random(10), my3 = -2 + random(10); +int8_t mx4 = -2 + random(10), my4 = -2 + random(10); + +class RotationExampleWindow : public SDLWindowRGB565 { + public: + RotationExampleWindow(Graphics2D* gfx2d, int w, int h) : SDLWindowRGB565(gfx2d, w, h) {} + void setup() { + loadPNG(&sprites, "../leafs_128_32_4x.png"); + leaf0.enableMask(maskColor); + leaf1.enableMask(maskColor); + leaf2.enableMask(maskColor); + leaf3.enableMask(maskColor); + // create single sprites, easier for drawing + leaf0.drawGraphics2D(0, 0, &sprites, 0 * 32, 0, 32, 32); + leaf1.drawGraphics2D(0, 0, &sprites, 1 * 32, 0, 32, 32); + leaf2.drawGraphics2D(0, 0, &sprites, 2 * 32, 0, 32, 32); + leaf3.drawGraphics2D(0, 0, &sprites, 3 * 32, 0, 32, 32); + + gfx2d.enableMask(rgb565(0, 0, 0)); // default (0,0,0) + waterBackground.enableMask(rgb565(0, 0, 0)); // default (0,0,0) + // gfx2d.setMaskColor(rgb565(255, 255, 255)); + } + + void loop() { + static uint16_t counter = 1; + counter++; + + if (counter % 10 == 0) { + lx1 += mx1; + ly1 += my1; + lx2 += mx2; + ly2 += my2; + lx3 += mx3; + ly3 += my3; + lx4 += mx4; + ly4 += my4; + } + + uint16_t r1 = random(WATER_W - 4); + uint16_t r2 = random(WATER_H - 4); + + // randomize water + for (uint16_t x = r1; x < r1 + 3; x++) { + for (uint16_t y = r2; y < r2 + 3; y++) { + wbuf1[x + WATER_W * y] = 127; + } + } + + waterBackground.fillFrame(0, 0, 240, 240, rgb565(10, 10, 10)); + + // gfx2d.enableAlpha(.5); + waterBackground.drawGraphics2D_rotated(lx1, ly1, &leaf0, 16, 16, counter / 50.0); + waterBackground.drawGraphics2D_rotated(lx2, ly2, &leaf1, 16, 16, -counter / 50.0); + waterBackground.drawGraphics2D_rotated(lx3, ly3, &leaf2, 16, 16, counter / 50.0); + waterBackground.drawGraphics2D_rotated(lx4, ly4, &leaf3, 16, 16, -counter / 50.0); + // gfx2d.disableAplha(); + + calcWater(wbuf1, wbuf2, WATER_W, WATER_H, .9); + mapWater(wbuf1, WATER_W, WATER_H, &waterBackground, &waterScreenBuffer, 0, 0); + std::swap(wbuf1, wbuf2); + + gfx2d.drawGraphics2D_2x(0, 0, &waterScreenBuffer); + + delay(1000 / 30); + } +}; + +int main(int argc, char* argv[]) { + RotationExampleWindow exampleWindow(&gfx2d, BUF_W, BUF_H); + exampleWindow.run(); + return 0; +} diff --git a/src/animations/demos/rotation/watchface01.png b/src/animations/demos/rotation/watchface01.png new file mode 100644 index 000000000..2ac31fe6c Binary files /dev/null and b/src/animations/demos/rotation/watchface01.png differ diff --git a/src/animations/demos/rotation/watchface01h.png b/src/animations/demos/rotation/watchface01h.png new file mode 100644 index 000000000..fd59c94b8 Binary files /dev/null and b/src/animations/demos/rotation/watchface01h.png differ diff --git a/src/animations/demos/rotation/watchface01m.png b/src/animations/demos/rotation/watchface01m.png new file mode 100644 index 000000000..14aff30c1 Binary files /dev/null and b/src/animations/demos/rotation/watchface01m.png differ diff --git a/src/animations/demos/shapes.cpp.inactive b/src/animations/demos/shapes.cpp.inactive new file mode 100644 index 000000000..2752b04c9 --- /dev/null +++ b/src/animations/demos/shapes.cpp.inactive @@ -0,0 +1,109 @@ +#include + +#include + +#include "../../FakeArduino.h" +#include "../../FakeArduinoWindowSDL.h" +#include "../../gfx_2d.h" +#include "../../gfx_util.h" +#include "../../math_angles.h" +using namespace std; + +#define BUF_W 240 +#define BUF_H 240 +uint16_t screenBuffer[BUF_W * BUF_H]; + +Graphics2D gfx2d(BUF_W, BUF_H, 16, false); + +class WatchSimpleWindow : public SDLWindowRGB565 { + public: + WatchSimpleWindow(Graphics2D* gfx2d, int w, int h) : SDLWindowRGB565(gfx2d, w, h) {} + void setup() {} + + void loop() { + uint8_t cx = 119; + uint8_t cy = 119; + + gfx2d.fill(rgb565(0, 0, 0)); + + // gfx2d.drawLine(0, 0, x, x, rgb565(x, x, 255)); + // gfx2d.fillFrame(10, 10, 10, 10, rgb565(x, x, 100)); + // gfx2d.drawCircle(120, 120, 32, rgb565(x, 255, 255)); + // gfx2d.fillFrame(10, 10, 10, 10, rgb565(255, x, 100)); + // gfx2d.drawEllipse(20, 20, 5, 20, rgb565(255, 0, x)); + // gfx2d.fillEllipse(120, 20, 5, 20, rgb565(255, 0, x)); + // gfx2d.fillFrame(10, 10, 10, 10, rgb565(255, 255, 100)); + // gfx2d.drawEllipse(20, 20, 5, 20, rgb565(255, x, 0)); + // gfx2d.fillEllipse(120, 20, 5, 20, rgb565(255, 0, x)); + // gfx2d.fillRFrame(40, 40, 20, 30, 4, rgb565(255, x, 100)); + // gfx2d.drawRFrame(60, 60, 30, 20, 4, rgb565(255, x, x)); + + // gfx2d.drawCircle(119, 119, 119, rgb565(255, 255, 255)); + // gfx2d.drawCircle(119, 120, 119, rgb565(255, 255, 255)); + // gfx2d.drawCircle(120, 119, 119, rgb565(255, 255, 255)); + // gfx2d.drawCircle(120, 120, 119, rgb565(255, 255, 255)); + + // gfx2d.drawThickLine(0, 0, 120, 120, 4, rgb565(255, 0, 0)); + // gfx2d.drawThickLine(240, 0, 120, 120, 5, rgb565(0, 255, 0)); + // gfx2d.drawThickLine(0, 240, 120, 120, 6, rgb565(0, 0, 255)); + // gfx2d.drawThickLine(240, 240, 120, 120, 7, rgb565(255, 255, 0)); + + // gfx2d.fill(rgb565(0, 255, 100)); + // gfx2d.fillCircle(120, 120, 20, rgb565(255, 255, 255)); + + // drawArc(120, 120, -90, 90, 90, 110, 6, rgb565(128, 128, 128)); + // drawArc(120, 120, -90, 90, 90, 110, 5, rgb565(255, 255, 255)); + // drawArc(120, 120, -90, 90, 90, 110, 4, rgb565(0, 32, 0)); + // drawArc(120, 120, -90, 44, 90, 110, 4, rgb565(0, 255, 0)); + + gfx2d.drawArc(120, 120, 0, 360, 90, 113, 5, rgb565(32, 32, 32)); + // gfx2d.drawMinuteTicks(120, 120, 116, 50, rgb565(255, 0, 0)); + gfx2d.drawHourTicks(120, 120, 117, 107, rgb565(128, 128, 128)); + + gfx2d.drawArc(120, 120, 0, 360, 90, 93, 7, changeColor(rgb565(210, 50, 66), 0.25)); + gfx2d.drawArc(120, 120, 0, 280, 90, 93, 7, dimColor(rgb565(210, 50, 66), 25)); + gfx2d.drawArc(120, 120, 0, 280, 90, 93, 6, rgb565(210, 50, 66)); + + gfx2d.drawArc(120, 120, 0, 360, 90, 75, 7, changeColor(rgb565(117, 235, 10), 0.25)); + gfx2d.drawArc(120, 120, 0, 128, 90, 75, 7, dimColor(rgb565(117, 235, 10), 25)); + gfx2d.drawArc(120, 120, 0, 128, 90, 75, 6, rgb565(117, 235, 10)); + + gfx2d.drawArc(120, 120, 0, 360, 90, 57, 7, changeColor(rgb565(25, 193, 202), 0.25)); + gfx2d.drawArc(120, 120, 0, 32, 90, 57, 7, dimColor(rgb565(25, 193, 202), 25)); + gfx2d.drawArc(120, 120, 0, 32, 90, 57, 6, rgb565(25, 193, 202)); + + static uint32_t ticks = 0; + ticks++; + float deltaAngle = ticks; + // hours + gfx2d.drawThickTick(120, 120, 0, 16, -66 + deltaAngle / (3600), 1, rgb565(255, 255, 255)); + gfx2d.drawThickTick(120, 120, 16, 60, -66 + deltaAngle / (3600), 4, rgb565(255, 255, 255)); + + // minutes + gfx2d.drawThickTick(120, 120, 0, 16, 45 + deltaAngle / (60), 1, rgb565(255, 255, 255)); + gfx2d.drawThickTick(120, 120, 16, 105, 45 + deltaAngle / (60), 4, rgb565(255, 255, 255)); + + // seconds + gfx2d.fillCircle(120, 120, 3, rgb565(255, 0, 0)); + gfx2d.drawThickTick(120, 120, 0, 16, 0 + deltaAngle, 1, rgb565(255, 0, 0)); + gfx2d.drawThickTick(120, 120, 0, 110, 180 + deltaAngle, 1, rgb565(255, 0, 0)); + + // moon + gfx2d.fillCircle(120, 230, 9, rgb565(128, 128, 128)); + gfx2d.fillCircle(120, 230, 8, rgb565(255, 255, 255)); + gfx2d.fillCircle(123, 230, 6, rgb565(0, 0, 0)); + + // usb connector + gfx2d.fillFrame(104, 10, 13, 2, rgb565(128, 128, 128)); // cable dot + gfx2d.fillFrame(117, 8, 3, 6, rgb565(200, 200, 200)); // cable to casing + gfx2d.fillFrame(124, 8, 11, 6, rgb565(128, 128, 128)); // connector + gfx2d.fillFrame(120, 6, 8, 10, rgb565(200, 200, 200)); // casing + delay(1000 / 30); + } +}; + +int main(int argc, char* argv[]) { + WatchSimpleWindow wsw(&gfx2d, BUF_W, BUF_H); + wsw.run(); + return 0; +} diff --git a/src/animations/demos/text.cpp.inactive b/src/animations/demos/text.cpp.inactive new file mode 100644 index 000000000..e150955a1 --- /dev/null +++ b/src/animations/demos/text.cpp.inactive @@ -0,0 +1,100 @@ +#include +#include + +#include "../../FakeArduino.h" +#include "../../FakeArduinoWindowSDL.h" +#include "../../gfx_2d_print.h" +#include "../../gfx_util.h" + +#include "../../fonts/Picopixel.h" +#include "../../fonts/FreeSans11pt8b.h" + +using namespace std; + +#define BUF_W 240 +#define BUF_H 240 + +Graphics2DPrint gfx2d(BUF_W, BUF_H, 16); + +// uint16_t maskColor = rgb565(0, 0, 0); + +class RotationExampleWindow : public SDLWindowRGB565 { + public: + RotationExampleWindow(Graphics2D* gfx2d, int w, int h) : SDLWindowRGB565(gfx2d, w, h) {} + void setup() { + // gfx2d.enableMask(rgb565(0, 0, 0)); + } + + void loop() { + static uint16_t counter = 1; + Graphics2DPrint* gfx = &gfx2d; + + counter++; + gfx->fill(rgb565(0, 0, 0)); + + gfx->setTextSize(1); + gfx->setTextLeftAligned(); + gfx->setTextCursor(120, 20); + gfx->print("Left\naligned"); + gfx->setTextRightAligned(); + gfx->setTextCursor(120, 40); + gfx->print("Right\naligned"); + + gfx->setTextCursor(0, 120); + gfx->setTextLeftAligned(); + gfx->setTextMiddleAligned(); + gfx->print("middle1\nmiddle2"); + + gfx->setTextCenterAligned(); + gfx->setTextTopAligned(); + gfx->setTextCursor(120, 120); + gfx->print("Top aligned\n(top supports multiple_rows)"); + + gfx->setTextCenterAligned(); + gfx->setTextBottomAligned(); + gfx->setTextCursor(120, 120); + gfx->print("Bottom aligned"); // does not support multiple rows + + gfx->setTextCenterAligned(); + gfx->setTextBottomAligned(); + gfx->setTextCursor(120, 150); + gfx->setTextSize(2); + gfx->print("Some specifics \n characters \n \xA4 \xDF \xE0"); // does not support multiple rows + + gfx->setFont(&Picopixel); + gfx->setTextCenterAligned(); + gfx->setTextCursor(120,200); + gfx->setTextSize(1); + gfx->print("Font pico Pixel"); + gfx->clearFont(); + + gfx->setFont(&FreeSans11pt8b); + gfx->setTextCenterAligned(); + gfx->setTextCursor(120,220); + gfx->setTextSize(1); + gfx->print("Font Serif Bold 9px"); + gfx->clearFont(); + + + /* + gfx->fill(rgb565(0, 0, 0)); + + int i = 0; + int j = 0; + gfx->setTextSize(1); + for(i=0; i<11; i++){ + for(j=0;j<17;j++){ + gfx->setTextCursor(j*15+10,i*15+10); + gfx->print(char(i*10+j+160)); + } + } + */ + + } +}; + +int main(int argc, char* argv[]) { + RotationExampleWindow exampleWindow(&gfx2d, BUF_W, BUF_H); + exampleWindow.run(); + return 0; +} diff --git a/src/animations/demos/watch-simple.cpp.inactive b/src/animations/demos/watch-simple.cpp.inactive new file mode 100644 index 000000000..b4ccf7e41 --- /dev/null +++ b/src/animations/demos/watch-simple.cpp.inactive @@ -0,0 +1,48 @@ +#include + +#include + +#include "../../FakeArduino.h" +#include "../../FakeArduinoWindowSDL.h" +#include "../../gfx_2d.h" +#include "../../gfx_util.h" +#include "../../math_angles.h" +using namespace std; + +#define BUF_W 240 +#define BUF_H 240 +uint16_t screenBuffer[BUF_W * BUF_H]; + +Graphics2D gfx2d(BUF_W, BUF_H, 16); + +class WatchSimpleWindow : public SDLWindowRGB565 { + public: + WatchSimpleWindow(Graphics2D* gfx2d, int w, int h) : SDLWindowRGB565(gfx2d, w, h) {} + void setup() {} + + void loop() { + delay(1000 / 30); // 30FPS + static uint x = 0; + x++; + + uint8_t cx = 119; + uint8_t cy = 119; + + gfx2d.fill(rgb565(1, 1, 1)); + + gfx2d.drawHourTicks(BUF_W / 2, BUF_H / 2, 100, 90, rgb565(255, 255, 255)); + // hour + gfx2d.drawLine(cx, cy, rpx(cx, 33, h2d(x)), rpy(cy, 33, h2d(x)), rgb565(255, 255, 255)); + // // minute + gfx2d.drawLine(cx, cy, rpx(cx, 66, m2d(x)), rpy(cy, 66, m2d(x)), rgb565(0, 255, 0)); + // // second + gfx2d.drawLine(cx, cy, rpx(cx, 15, s2d(x) + 180), rpy(cy, 15, s2d(x) + 180), rgb565(255, 0, 0)); // short backwards + gfx2d.drawLine(cx, cy, rpx(cx, 90, s2d(x)), rpy(cy, 90, s2d(x)), rgb565(255, 0, 0)); // long front + } +}; + +int main(int argc, char* argv[]) { + WatchSimpleWindow wsw(&gfx2d, BUF_W, BUF_H); + wsw.run(); + return 0; +} diff --git a/src/animations/demos/water.cpp.inactive b/src/animations/demos/water.cpp.inactive new file mode 100644 index 000000000..26b4f8f9c --- /dev/null +++ b/src/animations/demos/water.cpp.inactive @@ -0,0 +1,65 @@ +#include + +#include + +#include "../../anim_water_ripple.h" +#include "../../gfx_2d.h" +#include "../../gfx_util.h" +#include "../../FakeArduino.h" +#include "../../FakeArduinoWindowSDL.h" + +using namespace std; + +#define BUF_W 240 +#define BUF_H 240 +#define WATER_W 120 +#define WATER_H 120 + +int8_t wbuf1[WATER_W * WATER_H]; +int8_t wbuf2[WATER_W * WATER_H]; + +Graphics2D gfx2d(BUF_W, BUF_H, 16); +Graphics2D waterBackground(WATER_W, WATER_H, 8); +Graphics2D waterScreenBuffer(WATER_W, WATER_H, 8); + +// mix colors +int16_t mix(uint8_t base, float n1, float n2) { return (n1 - abs(n2)) * base; } + +class WaterWindow : public SDLWindowRGB565 { + public: + WaterWindow(Graphics2D* gfx2d, int w, int h) : SDLWindowRGB565(gfx2d, w, h) {} + void setup() { + for (uint16_t x = 0; x < WATER_W; x++) { + for (uint16_t y = 0; y < WATER_H; y++) { + waterBackground.drawPixel(x, y, rgb565(x % 255, y % 255, 0)); + } + } + } + + void loop() { + static uint16_t counter = 0; + uint16_t r1 = random(WATER_W - 4); + uint16_t r2 = random(WATER_H - 4); + + for (uint16_t x = r1; x < r1 + 3; x++) { + for (uint16_t y = r2; y < r2 + 3; y++) { + wbuf1[x + WATER_W * y] = 127; + } + } + + counter++; + calcWater(wbuf1, wbuf2, WATER_W, WATER_H, .9); + mapWater(wbuf1, WATER_W, WATER_H, &waterBackground, &waterScreenBuffer, 0, 0); + std::swap(wbuf1, wbuf2); + + gfx2d.drawGraphics2D_2x(0, 0, &waterScreenBuffer); + + delay(1000 / 30); + } +}; + +int main(int argc, char* argv[]) { + WaterWindow pw(&gfx2d, BUF_W, BUF_H); + pw.run(); + return 0; +} diff --git a/src/apps/_experiments/autumn.cpp b/src/apps/_experiments/autumn.cpp index 03d194314..a7ecabea0 100644 --- a/src/apps/_experiments/autumn.cpp +++ b/src/apps/_experiments/autumn.cpp @@ -1,7 +1,7 @@ #include "./apps/_experiments/autumn.h" -#include +#include #include #include #include diff --git a/src/apps/_experiments/dnatilt.cpp b/src/apps/_experiments/dnatilt.cpp index eba88c437..2391bf109 100644 --- a/src/apps/_experiments/dnatilt.cpp +++ b/src/apps/_experiments/dnatilt.cpp @@ -22,9 +22,9 @@ void OswAppDNATilt::loop() { OswHal* hal = OswHal::getInstance(); if (millis() - lastDraw > 250 /* 4fps redraw */) { lastDraw = millis(); - hal->getCanvas()->getGraphics2D()->fill(rgb565(0, 0, 0)); + hal->getCanvas()->fill(rgb565(0, 0, 0)); hal->getCanvas()->setTextColor(rgb565(255, 255, 255)); - hal->getCanvas()->setCursor(20, 100); + hal->getCanvas()->setTextCursor(20, 100); hal->getCanvas()->setTextSize(2); if (hal->environment->getAccelerationX() > 250) { diff --git a/src/apps/_experiments/fireworks.cpp b/src/apps/_experiments/fireworks.cpp index f2d448f9d..bff357ca5 100644 --- a/src/apps/_experiments/fireworks.cpp +++ b/src/apps/_experiments/fireworks.cpp @@ -1,7 +1,7 @@ #include "./apps/_experiments/fireworks.h" -#include +#include #include #include #include @@ -39,10 +39,10 @@ void OswAppFireworks::loop() { } gfx2d->dim(10); } else { - hal->getCanvas()->getGraphics2D()->fill(0); // bg black + hal->getCanvas()->fill(0); // bg black hal->getCanvas()->setTextColor(rgb565(255, 255, 255)); hal->getCanvas()->setTextSize(3); - hal->getCanvas()->setCursor(50, 110); + hal->getCanvas()->setTextCursor(50, 110); hal->getCanvas()->print("23:59:"); hal->getCanvas()->print(60 - countdown); countdown--; diff --git a/src/apps/_experiments/gif_player.cpp b/src/apps/_experiments/gif_player.cpp index 2fb67d035..4896f88f5 100644 --- a/src/apps/_experiments/gif_player.cpp +++ b/src/apps/_experiments/gif_player.cpp @@ -1,3 +1,4 @@ +#ifndef OSW_EMULATOR #include "./apps/_experiments/gif_player.h" @@ -117,3 +118,5 @@ void OswAppGifPlayer::stop() { gif.close(); } } + +#endif \ No newline at end of file diff --git a/src/apps/_experiments/power_demo.cpp b/src/apps/_experiments/power_demo.cpp index 1a6256b7b..226c955a5 100644 --- a/src/apps/_experiments/power_demo.cpp +++ b/src/apps/_experiments/power_demo.cpp @@ -20,82 +20,82 @@ void OswAppPowerDemo::loop() { static long loopCount = 0; loopCount++; OswHal* hal = OswHal::getInstance(); - hal->getCanvas()->fillScreen(0); + hal->getCanvas()->fill(0); hal->getCanvas()->setTextColor(rgb565(255, 255, 255)); - hal->getCanvas()->setCursor(24, 119); + hal->getCanvas()->setTextCursor(24, 119); hal->setCPUClock(240); hal->setBrightness(255); - hal->getCanvas()->setCursor(40, 120); + hal->getCanvas()->setTextCursor(40, 120); drawColors(); hal->getCanvas()->print("100% Brightness / 240MHz"); hal->flushCanvas(); delay(3000); hal->setBrightness(255 * .75); - hal->getCanvas()->setCursor(40, 120); + hal->getCanvas()->setTextCursor(40, 120); drawColors(); hal->getCanvas()->print(" 75% Brightness / 240MHz"); hal->flushCanvas(); delay(3000); hal->setBrightness(255 * .5); - hal->getCanvas()->setCursor(40, 120); + hal->getCanvas()->setTextCursor(40, 120); drawColors(); hal->getCanvas()->print(" 50% Brightness / 240MHz"); hal->flushCanvas(); delay(3000); hal->setBrightness(255 * .25); - hal->getCanvas()->setCursor(40, 120); + hal->getCanvas()->setTextCursor(40, 120); drawColors(); hal->getCanvas()->print(" 25% Brightness / 240MHz"); hal->flushCanvas(); delay(3000); hal->setBrightness(255 * .10); - hal->getCanvas()->setCursor(40, 120); + hal->getCanvas()->setTextCursor(40, 120); drawColors(); hal->getCanvas()->print(" 10% Brightness / 240MHz"); hal->flushCanvas(); delay(3000); hal->setCPUClock(160); - hal->getCanvas()->setCursor(40, 120); + hal->getCanvas()->setTextCursor(40, 120); drawColors(); hal->getCanvas()->print(" 10% Brightness / 160MHz"); hal->flushCanvas(); delay(3000); hal->setCPUClock(80); - hal->getCanvas()->setCursor(40, 120); + hal->getCanvas()->setTextCursor(40, 120); drawColors(); hal->getCanvas()->print(" 10% Brightness / 80MHz "); hal->flushCanvas(); delay(3000); hal->setCPUClock(40); - hal->getCanvas()->setCursor(40, 120); + hal->getCanvas()->setTextCursor(40, 120); drawColors(); hal->getCanvas()->print(" 10% Brightness / 40MHz "); hal->flushCanvas(); delay(3000); hal->setCPUClock(20); - hal->getCanvas()->setCursor(40, 120); + hal->getCanvas()->setTextCursor(40, 120); drawColors(); hal->getCanvas()->print(" 10% Brightness / 20MHz "); hal->flushCanvas(); delay(3000); hal->setCPUClock(10); - hal->getCanvas()->setCursor(40, 120); + hal->getCanvas()->setTextCursor(40, 120); drawColors(); hal->getCanvas()->print(" 10% Brightness / 10MHz "); hal->flushCanvas(); delay(3000); - hal->getCanvas()->setCursor(40, 120); + hal->getCanvas()->setTextCursor(40, 120); drawColors(); hal->getCanvas()->print(" going to deep sleep "); hal->flushCanvas(); diff --git a/src/apps/_experiments/show_display_size.cpp b/src/apps/_experiments/show_display_size.cpp index b837cd236..5754264be 100644 --- a/src/apps/_experiments/show_display_size.cpp +++ b/src/apps/_experiments/show_display_size.cpp @@ -37,12 +37,9 @@ void OswAppShowDisplaySize::loop() { } } - // gfx->drawHLine(0, 119, 240, rgb565(255, 255, 255)); - // gfx->drawVLine(119, 0, 240, rgb565(255, 255, 255)); - OswHal* hal = OswHal::getInstance(); hal->getCanvas()->setTextColor(rgb565(255, 255, 255)); - hal->getCanvas()->setCursor(24, 119); + hal->getCanvas()->setTextCursor(24, 119); hal->getCanvas()->print("Chunk Height: "); hal->getCanvas()->print(chunkHeight); } diff --git a/src/apps/games/snake_game.cpp b/src/apps/games/snake_game.cpp index b0f7f6bf6..5565cf8f0 100644 --- a/src/apps/games/snake_game.cpp +++ b/src/apps/games/snake_game.cpp @@ -87,28 +87,28 @@ void OswAppSnakeGame::drawPlayer() { int xSize = snake[i][0] - snake[i + 1][0]; int ySize = snake[i][1] - snake[i + 1][1]; - hal->getCanvas()->fillRoundRect(min(snake[i][0], snake[i + 1][0]) * cellSize + 2, // - min(snake[i][1], snake[i + 1][1]) * cellSize + 2, // - cellSize * (1 + abs(xSize)) - 3, // - cellSize * (1 + abs(ySize)) - 3, // - 3, // - ui->getSuccessColor()); + hal->getCanvas()->fillRFrame(min(snake[i][0], snake[i + 1][0]) * cellSize + 2, // + min(snake[i][1], snake[i + 1][1]) * cellSize + 2, // + cellSize * (1 + abs(xSize)) - 3, // + cellSize * (1 + abs(ySize)) - 3, // + 3, // + ui->getSuccessColor()); } else { - hal->getCanvas()->fillRoundRect(snake[i][0] * cellSize + 2, // - snake[i][1] * cellSize + 2, // - cellSize - 3, // - cellSize - 3, // - 3, // - ui->getSuccessColor()); + hal->getCanvas()->fillRFrame(snake[i][0] * cellSize + 2, // + snake[i][1] * cellSize + 2, // + cellSize - 3, // + cellSize - 3, // + 3, // + ui->getSuccessColor()); } } } - hal->getCanvas()->fillRoundRect(snake[0][0] * cellSize, // - snake[0][1] * cellSize, // - cellSize, // - cellSize, // - 5, // - ui->getSuccessColor()); + hal->getCanvas()->fillRFrame(snake[0][0] * cellSize, // + snake[0][1] * cellSize, // + cellSize, // + cellSize, // + 5, // + ui->getSuccessColor()); } } @@ -120,13 +120,13 @@ void OswAppSnakeGame::drawGameState() { 150, 10, // ui->getForegroundColor()); } else { - hal->getCanvas()->fillRect(140, 5, 2, 10, ui->getForegroundColor()); - hal->getCanvas()->fillRect(143, 5, 2, 10, ui->getForegroundColor()); + hal->getCanvas()->fillFrame(140, 5, 2, 10, ui->getForegroundColor()); + hal->getCanvas()->fillFrame(143, 5, 2, 10, ui->getForegroundColor()); } } void OswAppSnakeGame::drawLunch() { - OswHal::getInstance()->getCanvas()->fillRoundRect(mealXCoord * cellSize + 2, // + OswHal::getInstance()->getCanvas()->fillRFrame(mealXCoord * cellSize + 2, // mealYCoord * cellSize + 2, // cellSize - 2, // cellSize - 2, // @@ -141,7 +141,7 @@ void OswAppSnakeGame::drawGrid() { int yTarget = y * cellSize + 20; if (coordsInGame(xTarget, yTarget)) { - OswHal::getInstance()->getCanvas()->drawRect(x * cellSize, 20 + y * cellSize, cellSize, cellSize, ui->getForegroundDimmedColor()); + OswHal::getInstance()->getCanvas()->drawFrame(x * cellSize, 20 + y * cellSize, cellSize, cellSize, ui->getForegroundDimmedColor()); } } } diff --git a/src/apps/main/map.cpp b/src/apps/main/map.cpp index 8d76deb16..843fd8a43 100644 --- a/src/apps/main/map.cpp +++ b/src/apps/main/map.cpp @@ -1,9 +1,10 @@ +#ifndef OSW_EMULATOR #include "./apps/main/map.h" #include #include -#include +#include #include #include #include @@ -197,7 +198,7 @@ void OswAppMap::loop() { gfx->fill(rgb565(0, 0, 0)); hal->getCanvas()->setTextColor(rgb565(255, 255, 255)); - hal->getCanvas()->setCursor(20, 120); + hal->getCanvas()->setTextCursor(20, 120); drawTilesBuffered(tileBuffer, BUF_LEN, gfx, (loadTile)loadTileFn, lat, lon, z); @@ -231,3 +232,5 @@ void OswAppMap::stop() { } #endif + +#endif \ No newline at end of file diff --git a/src/apps/main/switcher.cpp b/src/apps/main/switcher.cpp index 4c0954b50..6550b5348 100644 --- a/src/apps/main/switcher.cpp +++ b/src/apps/main/switcher.cpp @@ -87,9 +87,9 @@ void OswAppSwitcher::loop() { uint16_t x = (DISP_W / 2) + (cos(alpha * PI / 180) * r); uint16_t y = (DISP_H / 2) + (sin(alpha * PI / 180) * r); if(i == *_rtcAppIndex) { - hal->getCanvas()->getGraphics2D()->fillCircle(x, y, rDot, OswUI::getInstance()->getInfoColor()); + hal->getCanvas()->fillCircle(x, y, rDot, OswUI::getInstance()->getInfoColor()); } else { - hal->getCanvas()->getGraphics2D()->fillCircle(x, y, rDot, OswUI::getInstance()->getForegroundColor()); + hal->getCanvas()->fillCircle(x, y, rDot, OswUI::getInstance()->getForegroundColor()); } } } diff --git a/src/apps/tools/OswAppBLEMEdiaCtrl.cpp b/src/apps/tools/OswAppBLEMediaCtrl.cpp similarity index 77% rename from src/apps/tools/OswAppBLEMEdiaCtrl.cpp rename to src/apps/tools/OswAppBLEMediaCtrl.cpp index cc5c609d5..e43236e5e 100644 --- a/src/apps/tools/OswAppBLEMEdiaCtrl.cpp +++ b/src/apps/tools/OswAppBLEMediaCtrl.cpp @@ -1,6 +1,6 @@ - +#ifndef OSW_EMULATOR #ifdef OSW_FEATURE_BLE_MEDIA_CTRL -#include "./apps/tools/OswAppBLEMEdiaCtrl.h" +#include "./apps/tools/OswAppBLEMediaCtrl.h" #include #include @@ -10,13 +10,13 @@ BleKeyboard* bleKeyboard; -void OswAppBLEMEdiaCtrl::setup() { +void OswAppBLEMediaCtrl::setup() { OswHal::getInstance()->disableDisplayBuffer(); bleKeyboard = new BleKeyboard(BLE_DEVICE_NAME, "p3dt", 100); bleKeyboard->begin(); } -void OswAppBLEMEdiaCtrl::loop() { +void OswAppBLEMediaCtrl::loop() { static long lastDraw = 0; static bool fillScreen = true; Serial.println(ESP.getFreeHeap()); @@ -35,25 +35,25 @@ void OswAppBLEMEdiaCtrl::loop() { if (fillScreen) { fillScreen = false; - hal->getCanvas()->getGraphics2D()->fill(rgb565(0, 0, 0)); + hal->getCanvas()->fill(rgb565(0, 0, 0)); } hal->getCanvas()->setTextColor(rgb565(255, 255, 255)); hal->getCanvas()->setTextSize(2); if (bleKeyboard->isConnected()) { - hal->getCanvas()->setCursor(20, 130); + hal->getCanvas()->setTextCursor(20, 130); hal->getCanvas()->print(LANG_CONNECTED); - hal->getCanvas()->setCursor(100, 50); + hal->getCanvas()->setTextCursor(100, 50); hal->getCanvas()->print(LANG_BMC_VOLUME); hal->getCanvas()->print(" + "); - hal->getCanvas()->setCursor(100, 190); + hal->getCanvas()->setTextCursor(100, 190); hal->getCanvas()->print(LANG_BMC_VOLUME); hal->getCanvas()->print(" - "); } else { - hal->getCanvas()->setCursor(20, 110); + hal->getCanvas()->setTextCursor(20, 110); hal->getCanvas()->print(LANG_BMC_CONNECTING); } @@ -61,9 +61,10 @@ void OswAppBLEMEdiaCtrl::loop() { } } -void OswAppBLEMEdiaCtrl::stop() { +void OswAppBLEMediaCtrl::stop() { bleKeyboard->end(); delete bleKeyboard; OswHal::getInstance()->enableDisplayBuffer(); } +#endif #endif \ No newline at end of file diff --git a/src/apps/tools/OswAppDistStats.cpp b/src/apps/tools/OswAppDistStats.cpp index ce5948d44..91cf001d6 100644 --- a/src/apps/tools/OswAppDistStats.cpp +++ b/src/apps/tools/OswAppDistStats.cpp @@ -2,6 +2,7 @@ #include "./apps/tools/OswAppDistStats.h" #include "./apps/watchfaces/OswAppWatchfaceFitness.h" +#include "./apps/tools/OswAppStepStats.h" #include #include @@ -45,23 +46,7 @@ void OswAppDistStats::showStickChart() { hal->gfx()->print(LANG_DISTSTATS_TITLE); OswAppDistStats::drawChart(); - - uint8_t coord_x = 30; - - hal->gfx()->drawThickTick(coord_x, 150, 0, DISP_W - (coord_x * 2), 90, 2, ui->getPrimaryColor()); - uint32_t tmpCursor = cursorPos; - hal->gfx()->setTextSize(1); - hal->gfx()->setTextCenterAligned(); - hal->gfx()->setTextBottomAligned(); - hal->gfx()->setTextCursor(DISP_W/2, 170); - hal->gfx()->print(hal->getLocalWeekday(&tmpCursor)); - hal->gfx()->setTextCursor(DISP_W/2, 190); - hal->gfx()->print(String(OswAppWatchfaceFitness::calculateDistance(hal->environment->getStepsOnDay(tmpCursor,true))) ); // lastweek(before 7 day) - hal->gfx()->setTextCursor(DISP_W/2, 215); - hal->gfx()->print(String(OswAppWatchfaceFitness::calculateDistance(hal->environment->getStepsAverage())) + String("/") + String(OswAppWatchfaceFitness::calculateDistance(hal->environment->getStepsTotalWeek()))); // Avg/Total - hal->gfx()->setTextSize(2); - hal->gfx()->setTextCursor(DISP_W/2, 205); - hal->gfx()->print(String(OswAppWatchfaceFitness::calculateDistance(hal->environment->getStepsOnDay(tmpCursor)) + String(" m"))); // Big font Fitness value + OswAppStepStats::drawInfoPanel(ui, (uint32_t)cursorPos, OswAppWatchfaceFitness::calculateDistance(hal->environment->getStepsOnDay((uint32_t)cursorPos, true)), OswAppWatchfaceFitness::calculateDistance(hal->environment->getStepsOnDay((uint32_t)cursorPos)), OswAppWatchfaceFitness::calculateDistance(hal->environment->getStepsAverage()), OswAppWatchfaceFitness::calculateDistance(hal->environment->getStepsTotalWeek()), " m"); } void OswAppDistStats::setup() { diff --git a/src/apps/tools/OswAppFitnessStats.cpp b/src/apps/tools/OswAppFitnessStats.cpp index 87cb77b4e..7b9fc5d52 100644 --- a/src/apps/tools/OswAppFitnessStats.cpp +++ b/src/apps/tools/OswAppFitnessStats.cpp @@ -53,13 +53,8 @@ void OswAppFitnessStats::setup() { } void OswAppFitnessStats::loop() { OswHal* hal = OswHal::getInstance(); - showFitnessTracking(); hal->requestFlush(); } -void OswAppFitnessStats::stop() { -#if defined(GPS_EDITION) || defined(GPS_EDITION_ROTATED) - OswHal::getInstance()->gpsBackupMode(); -#endif -} +void OswAppFitnessStats::stop() {} diff --git a/src/apps/tools/OswAppKcalStats.cpp b/src/apps/tools/OswAppKcalStats.cpp index 00b46478a..9cd659687 100644 --- a/src/apps/tools/OswAppKcalStats.cpp +++ b/src/apps/tools/OswAppKcalStats.cpp @@ -2,6 +2,7 @@ #include "./apps/tools/OswAppKcalStats.h" #include "./apps/watchfaces/OswAppWatchfaceFitness.h" +#include "./apps/tools/OswAppStepStats.h" #include #include @@ -69,23 +70,8 @@ void OswAppKcalStats::showCurvedChart() { OswAppKcalStats::drawCurvedChart(); - uint8_t coordX = 30; - hal->gfx()->drawThickTick(coordX, 150, 0, 240 - (coordX * 2), 90, 2, ui->getPrimaryColor()); - - // Data info uint32_t wDay = findCursorWeekDay(this->cursorPos); - hal->gfx()->setTextSize(1); - hal->gfx()->setTextCenterAligned(); - hal->gfx()->setTextBottomAligned(); - hal->gfx()->setTextCursor(DISP_W/2, 170); - hal->gfx()->print(hal->getLocalWeekday(&wDay)); - hal->gfx()->setTextCursor(DISP_W/2, 190); - hal->gfx()->print(String(OswAppWatchfaceFitness::calculateKcalorie(hal->environment->getStepsOnDay(wDay, true)))); // lastweek(before 7 day) - hal->gfx()->setTextCursor(DISP_W/2, 215); - hal->gfx()->print(String(OswAppWatchfaceFitness::calculateKcalorie(hal->environment->getStepsAverage())) + String("/") + String(OswAppWatchfaceFitness::calculateKcalorie(hal->environment->getStepsTotalWeek()))); // Avg/Total - hal->gfx()->setTextSize(2); - hal->gfx()->setTextCursor(DISP_W/2, 205); - hal->gfx()->print(String(OswAppWatchfaceFitness::calculateKcalorie(hal->environment->getStepsOnDay(wDay)) + String(" Kcal"))); // Big font Fitness value + OswAppStepStats::drawInfoPanel(ui, wDay, OswAppWatchfaceFitness::calculateKcalorie(hal->environment->getStepsOnDay(wDay, true)), OswAppWatchfaceFitness::calculateKcalorie(hal->environment->getStepsOnDay(wDay)), OswAppWatchfaceFitness::calculateKcalorie(hal->environment->getStepsAverage()), OswAppWatchfaceFitness::calculateKcalorie(hal->environment->getStepsTotalWeek()), " Kcal"); } void OswAppKcalStats::setup() {} diff --git a/src/apps/tools/OswAppPrintDebug.cpp b/src/apps/tools/OswAppPrintDebug.cpp index e9461d8a8..0d2d2bb63 100644 --- a/src/apps/tools/OswAppPrintDebug.cpp +++ b/src/apps/tools/OswAppPrintDebug.cpp @@ -38,7 +38,9 @@ void OswAppPrintDebug::loop() { #endif y = 32; +#ifndef OSW_EMULATOR printStatus("RAM", (String(ESP.getHeapSize() - ESP.getFreeHeap()) + "B / " + ESP.getHeapSize() + "B").c_str()); +#endif #ifdef BOARD_HAS_PSRAM printStatus("PSRAM", (String(ESP.getPsramSize() - ESP.getFreePsram()) + "B / " + String(ESP.getPsramSize()) + "B").c_str()); #endif @@ -76,7 +78,7 @@ void OswAppPrintDebug::loop() { #endif printStatus("Battery (Analog)", (wifiDisabled ? String(hal->getBatteryRaw()) : String("WiFi active!")).c_str()); char branchName[] = GIT_BRANCH_NAME; - printStatus("Hash", (String(GIT_COMMIT_HASH) + " (" + hal->gfx()->printSlice(branchName, 10, true) + ".." + ")").c_str()); + printStatus("Hash", (String(GIT_COMMIT_HASH) + " (" + hal->gfx()->slice(branchName, 10, true) + ".." + ")").c_str()); printStatus("Platform", String(PIO_ENV_NAME).c_str()); printStatus("Compiled", (String(__DATE__) + " " + String(__TIME__)).c_str()); diff --git a/src/apps/tools/OswAppStepStats.cpp b/src/apps/tools/OswAppStepStats.cpp index 0972242b5..6eb1c6823 100644 --- a/src/apps/tools/OswAppStepStats.cpp +++ b/src/apps/tools/OswAppStepStats.cpp @@ -22,7 +22,7 @@ void OswAppStepStats::drawChart() { uint32_t weekDayStep = hal->environment->getStepsOnDay(index); uint16_t chartStickValue = ((float)(weekDayStep > goalValue ? goalValue : weekDayStep) / goalValue) * chartStickHeight; - uint16_t barColor = OswConfigAllKeys::stepsPerDay.get() <= weekDayStep ? ui->getSuccessColor() : changeColor(ui->getSuccessColor(),2.85); + uint16_t barColor = OswConfigAllKeys::stepsPerDay.get() <= weekDayStep ? ui->getSuccessColor() : changeColor(ui->getSuccessColor(), 2.85); chartStickValue = chartStickValue < 2 ? 0 : chartStickValue; @@ -33,6 +33,25 @@ void OswAppStepStats::drawChart() { } } +void OswAppStepStats::drawInfoPanel(OswUI* ui, uint32_t pos, uint32_t lastWeekData, uint32_t todayData, uint32_t average, uint32_t total,const String& unit) { + OswHal* hal = OswHal::getInstance(); + + uint8_t coord_X = 30; + + hal->gfx()->drawThickTick(coord_X, 150, 0, DISP_W - (coord_X * 2), 90, 2, ui->getPrimaryColor()); + hal->gfx()->setTextSize(1); + hal->gfx()->setTextCenterAligned(); + hal->gfx()->setTextBottomAligned(); + hal->gfx()->setTextCursor(DISP_W / 2, 170); + hal->gfx()->print(hal->getLocalWeekday(&pos)); + hal->gfx()->setTextCursor(DISP_W / 2, 190); + hal->gfx()->print(String(lastWeekData)); // lastweek(before 7 day) + hal->gfx()->setTextCursor(DISP_W / 2, 215); + hal->gfx()->print(String(average) + String("/") + String(total)); // Avg/Total + hal->gfx()->setTextSize(2); + hal->gfx()->setTextCursor(DISP_W / 2, 205); + hal->gfx()->print(String(todayData) + unit); // Big font Fitness value +} void OswAppStepStats::showStickChart() { OswHal* hal = OswHal::getInstance(); @@ -45,23 +64,7 @@ void OswAppStepStats::showStickChart() { hal->gfx()->print(LANG_STEPSTATS_TITLE); OswAppStepStats::drawChart(); - - uint8_t coord_x = 30; - - hal->gfx()->drawThickTick(coord_x, 150, 0, DISP_W - (coord_x * 2), 90, 2, ui->getPrimaryColor()); - uint32_t tmpCursor = cursorPos; - hal->gfx()->setTextSize(1); - hal->gfx()->setTextCenterAligned(); - hal->gfx()->setTextBottomAligned(); - hal->gfx()->setTextCursor(DISP_W/2, 170); - hal->gfx()->print(hal->getLocalWeekday(&tmpCursor)); - hal->gfx()->setTextCursor(DISP_W/2, 190); - hal->gfx()->print(String(hal->environment->getStepsOnDay(tmpCursor, true))); // lastweek(before 7 day) - hal->gfx()->setTextCursor(DISP_W/2, 215); - hal->gfx()->print(String(hal->environment->getStepsAverage()) + String("/") + String(hal->environment->getStepsTotalWeek())); // Avg/Total - hal->gfx()->setTextSize(2); - hal->gfx()->setTextCursor(DISP_W/2, 205); - hal->gfx()->print(String(hal->environment->getStepsOnDay(tmpCursor))); // Big font Fitness value + OswAppStepStats::drawInfoPanel(ui,(uint32_t)cursorPos, hal->environment->getStepsOnDay((uint32_t)cursorPos, true), hal->environment->getStepsOnDay((uint32_t)cursorPos), hal->environment->getStepsAverage(), hal->environment->getStepsTotalWeek()); } void OswAppStepStats::setup() { diff --git a/src/apps/tools/water_level.cpp b/src/apps/tools/OswAppWaterLevel.cpp similarity index 93% rename from src/apps/tools/water_level.cpp rename to src/apps/tools/OswAppWaterLevel.cpp index 817fd07e5..eeb41cff3 100644 --- a/src/apps/tools/water_level.cpp +++ b/src/apps/tools/OswAppWaterLevel.cpp @@ -1,5 +1,5 @@ -#include "./apps/tools/water_level.h" +#include "./apps/tools/OswAppWaterLevel.h" #include #include @@ -47,8 +47,8 @@ void OswAppWaterLevel::circlesDisplay() { uint16_t color = isXYAccelerationInMiddle ? ui->getSuccessColor() : ui->getInfoColor(); - hal->getCanvas()->drawFastHLine(0, middleY, screenWidth, color); - hal->getCanvas()->drawFastVLine(middleX, 0, screenWidth, color); + hal->getCanvas()->drawHLine(0, middleY, screenWidth, color); + hal->getCanvas()->drawVLine(middleX, 0, screenWidth, color); const int x0 = middleX + xValue * 64; const int y0 = middleY - yValue * 64; @@ -62,7 +62,7 @@ void OswAppWaterLevel::circlesDisplay() { void OswAppWaterLevel::drawBar(const float value, char text, const int x) { OswHal* hal = OswHal::getInstance(); - Graphics2D* gfx = hal->getCanvas()->getGraphics2D(); + Graphics2D* gfx = hal->getCanvas(); const int fontHeight = 1; hal->getCanvas()->setTextSize(fontHeight); @@ -88,7 +88,7 @@ void OswAppWaterLevel::drawBar(const float value, char text, const int x) { gfx->drawCircle(x + 4, 120 + 2, width / 2 + 3, ui->getForegroundColor()); - hal->getCanvas()->setCursor(x + 2, 120 - 2); + hal->getCanvas()->setTextCursor(x + 2, 120 - 2); hal->getCanvas()->setTextColor(foregroundColor, backgroundColor); hal->getCanvas()->print(text); diff --git a/src/apps/tools/OswAppWebserver.cpp b/src/apps/tools/OswAppWebserver.cpp index 1d283f62d..e414ee8bd 100644 --- a/src/apps/tools/OswAppWebserver.cpp +++ b/src/apps/tools/OswAppWebserver.cpp @@ -18,14 +18,22 @@ void OswAppWebserver::loop() { OswHal* hal = OswHal::getInstance(); hal->gfx()->setTextSize(2); + // Configuration OswUI::getInstance()->setTextCursor(BUTTON_3); if (OswServiceAllTasks::wifi.isConnected()) { hal->gfx()->print(LANG_DISCONNECT); } else { if(OswServiceAllTasks::wifi.isEnabled()) hal->gfx()->print("..."); - else + else { hal->gfx()->print(LANG_CONNECT); + OswUI::getInstance()->setTextCursor(BUTTON_2); + if(OswConfigAllKeys::hostPasswordEnabled.get()) { + hal->gfx()->print(LANG_WEBSRV_AP_PASSWORD_ON); + } else { + hal->gfx()->print(LANG_WEBSRV_AP_PASSWORD_OFF); + } + } } if (hal->btnHasGoneDown(BUTTON_3)) { @@ -37,12 +45,16 @@ void OswAppWebserver::loop() { OswServiceAllTasks::wifi.connectWiFi(); } } - + if (hal->btnHasGoneDown(BUTTON_2)) { + if (!OswServiceAllTasks::wifi.isConnected()) { + OswServiceAllTasks::wifi.toggleAPPassword(); + } + } hal->gfx()->setTextSize(2); hal->gfx()->setTextCenterAligned(); hal->gfx()->setTextMiddleAligned(); - if (OswServiceAllTasks::wifi.isConnected()) { + if (OswServiceAllTasks::wifi.isConnected()) { // Wi-Fi connect info hal->gfx()->setTextCursor(120, OswServiceAllTasks::wifi.isStationEnabled() ? 60 : 90); hal->gfx()->setTextSize(1); hal->gfx()->setTextColor(ui->getPrimaryColor(), ui->getBackgroundColor()); @@ -54,7 +66,8 @@ void OswAppWebserver::loop() { hal->gfx()->setTextColor(ui->getInfoColor(), ui->getBackgroundColor()); hal->gfx()->println(LANG_WEBSRV_STATION_PWD); hal->gfx()->setTextSize(2); - hal->gfx()->println(OswServiceAllTasks::wifi.getStationPassword()); + const String pwd = OswServiceAllTasks::wifi.getStationPassword(); + hal->gfx()->println(pwd.isEmpty() ? "-" : pwd); } hal->gfx()->setTextSize(1); hal->gfx()->setTextColor(ui->getWarningColor(), ui->getBackgroundColor()); @@ -68,7 +81,7 @@ void OswAppWebserver::loop() { hal->gfx()->println(OswServiceAllTasks::webserver.getPassword()); hal->gfx()->setTextColor(ui->getForegroundColor(), ui->getBackgroundColor()); - } else { + } else { // No connect hal->gfx()->setTextCursor(120, 120); hal->gfx()->print(LANG_WEBSRV_TITLE); } diff --git a/src/apps/watchfaces/OswAppWatchface.cpp b/src/apps/watchfaces/OswAppWatchface.cpp index 52ec3fb96..27ffbc85e 100644 --- a/src/apps/watchfaces/OswAppWatchface.cpp +++ b/src/apps/watchfaces/OswAppWatchface.cpp @@ -3,7 +3,7 @@ // #define GIF_BG #ifdef ANIMATION -#include +#include #endif #include diff --git a/src/apps/watchfaces/OswAppWatchfaceBinary.cpp b/src/apps/watchfaces/OswAppWatchfaceBinary.cpp index 910ed2e37..7e32b744b 100644 --- a/src/apps/watchfaces/OswAppWatchfaceBinary.cpp +++ b/src/apps/watchfaces/OswAppWatchfaceBinary.cpp @@ -7,15 +7,13 @@ #include #include -#include "bma400_defs.h" - #define COLOR_SECxOND rgb565(231, 111, 81) #define COLOR_MIxNUTE rgb565(244, 162, 97) #define COLOR_HOxUR rgb565(42, 157, 143) #define COLOR_BLAxCK rgb565(0, 0, 0) #define COLOR_WHxITE rgb565(255, 255, 255) -void OswAppWatchfaceBinary::drawWatch(Graphics2D* gfx2d) { +void OswAppWatchfaceBinary::drawWatch() { uint32_t second = 0; uint32_t minute = 0; uint32_t hour = 0; @@ -82,7 +80,7 @@ void OswAppWatchfaceBinary::loop() { if(hal->btnHasGoneDown(BUTTON_2)) { hal->decreaseBrightness(25); } - drawWatch(hal->getCanvas()->getGraphics2D()); + drawWatch(); hal->requestFlush(); } diff --git a/src/apps/watchfaces/OswAppWatchfaceDigital.cpp b/src/apps/watchfaces/OswAppWatchfaceDigital.cpp index 84e85b07a..6064c4232 100644 --- a/src/apps/watchfaces/OswAppWatchfaceDigital.cpp +++ b/src/apps/watchfaces/OswAppWatchfaceDigital.cpp @@ -22,6 +22,17 @@ void OswAppWatchfaceDigital::refreshDateFormatCache() { OswAppWatchfaceDigital::dateFormatCache = (format == "mm/dd/yyyy" ? 1 : (format == "dd.mm.yyyy" ? 2 : 3)); } +// display Weekday to 3 charater +void OswAppWatchfaceDigital::displayWeekDay3(const char* weekday) { + OswHal* hal = OswHal::getInstance(); + + char weekday3[4]; + weekday3[0] = weekday[0]; + weekday3[1] = weekday[1]; + weekday3[2] = weekday[2]; + weekday3[3] = '\0'; + hal->gfx()->print(weekday3); +} void OswAppWatchfaceDigital::dateOutput(uint32_t yearInt, uint32_t monthInt, uint32_t dayInt) { OswHal* hal = OswHal::getInstance(); switch (OswAppWatchfaceDigital::getDateFormat()) { @@ -65,14 +76,7 @@ void drawDate(short timeZone, uint8_t fontSize, uint8_t CoordY) { hal->gfx()->setTextLeftAligned(); hal->gfx()->setTextCursor(120 - hal->gfx()->getTextOfsetColumns(6.9), CoordY); - { - char weekday3[4]; - weekday3[0] = weekday[0]; - weekday3[1] = weekday[1]; - weekday3[2] = weekday[2]; - weekday3[3] = '\0'; - hal->gfx()->print(weekday3); - } + OswAppWatchfaceDigital::displayWeekDay3(weekday); // The GFX library has an alignment bug, causing single letters to "float", therefore the workaround above is used to still utilize the correct string printing. //hal->gfx()->print(weekday[0]); diff --git a/src/apps/watchfaces/OswAppWatchfaceFitness.cpp b/src/apps/watchfaces/OswAppWatchfaceFitness.cpp index d31c232b5..41e15e991 100644 --- a/src/apps/watchfaces/OswAppWatchfaceFitness.cpp +++ b/src/apps/watchfaces/OswAppWatchfaceFitness.cpp @@ -34,14 +34,7 @@ void dateDisplay() { hal->gfx()->setTextRightAligned(); hal->gfx()->setTextCursor(205, 90); - { - char weekday3[4]; - weekday3[0] = weekday[0]; - weekday3[1] = weekday[1]; - weekday3[2] = weekday[2]; - weekday3[3] = '\0'; - hal->gfx()->print(weekday3); - } + OswAppWatchfaceDigital::displayWeekDay3(weekday); // Date hal->gfx()->setTextSize(2); diff --git a/src/apps/watchfaces/OswAppWatchfaceMix.cpp b/src/apps/watchfaces/OswAppWatchfaceMix.cpp index 07ecdd98c..62054955b 100644 --- a/src/apps/watchfaces/OswAppWatchfaceMix.cpp +++ b/src/apps/watchfaces/OswAppWatchfaceMix.cpp @@ -41,26 +41,13 @@ void OswAppWatchfaceMix::dateDisplay() { hal->getLocalDate(&dayInt, &monthInt, &yearInt); - // we want to output a value like "Wed, 05/02/2021" - hal->gfx()->setTextSize(1); hal->gfx()->setTextMiddleAligned(); hal->gfx()->setTextLeftAligned(); hal->gfx()->setTextCursor(DISP_W / 2 - OFF_SET_DATE_DIGITAL_WATCH_X_COORD, 75); - { - char weekday3[4]; - weekday3[0] = weekday[0]; - weekday3[1] = weekday[1]; - weekday3[2] = weekday[2]; - weekday3[3] = '\0'; - hal->gfx()->print(weekday3); - } + OswAppWatchfaceDigital::displayWeekDay3(weekday); - // The GFX library has an alignment bug, causing single letters to "float", therefore the workaround above is used to still utilize the correct string printing. - //hal->gfx()->print(weekday[0]); - //hal->gfx()->print(weekday[1]); - //hal->gfx()->print(weekday[2]); hal->gfx()->print(", "); // Date @@ -69,8 +56,6 @@ void OswAppWatchfaceMix::dateDisplay() { hal->gfx()->setTextLeftAligned(); hal->gfx()->setTextCursor(DISP_W / 2 - OFF_SET_DATE_DIGITAL_WATCH_X_COORD, 90); - // i really would want the date to be dynamic based on what's in the config, but the most efficient thing to do right - // now is simply three if statements covering the 3 common conditions. OswAppWatchfaceDigital::dateOutput(yearInt, monthInt, dayInt); } diff --git a/src/apps/watchfaces/OswAppWatchfaceMonotimer.cpp b/src/apps/watchfaces/OswAppWatchfaceMonotimer.cpp new file mode 100644 index 000000000..e016d3c7b --- /dev/null +++ b/src/apps/watchfaces/OswAppWatchfaceMonotimer.cpp @@ -0,0 +1,156 @@ + +#include "./apps/watchfaces/OswAppWatchfaceMonotimer.h" +// #define GIF_BG + +#include +#include +#include +#include +#include + +#ifdef GIF_BG +#include "./apps/_experiments/gif_player.h" +#endif + +/** + * @brief Draw N shifted ticks around the clock. + * + * @param cx center x axis + * @param cy center y axis + * @param r1 radius from the begin of the tick. + * @param r2 radius from the end of the tick. + * @param nTicks number of ticks to draw + * @param shift amount of degrees the ticks are shifted clockwise from the top (0°) + * @param color color code + */ +void OswAppWatchfaceMonotimer::drawNShiftedTicks(Graphics2D* gfx, uint8_t cx, uint8_t cy, uint8_t r1, uint8_t r2, uint8_t nTicks, float shift, uint16_t color) { + float deltaAngle = 360.0 / nTicks; + for (uint16_t i=0; idrawTick(cx, cy, r1, r2, (i * deltaAngle) + shift, color); + } +} + +/** + * @brief Draw N shifted ticks around the clock; skip every m-th. + * + * @param cx center x axis + * @param cy center y axis + * @param r1 radius from the begin of the tick. + * @param r2 radius from the end of the tick. + * @param nTicks number of ticks to draw + * @param shift amount of degrees the ticks are shifted clockwise from the top (0°) + * @param m do not draw every m-th tick + * @param color color code + */ +void OswAppWatchfaceMonotimer::drawNShiftedMaskedTicks(Graphics2D* gfx, uint8_t cx, uint8_t cy, uint8_t r1, uint8_t r2, uint8_t nTicks, float shift, uint16_t m, uint16_t color) { + float deltaAngle = 360.0 / nTicks; + for (uint16_t i=0; idrawTick(cx, cy, r1, r2, (i * deltaAngle) + shift, color); + } + } +} + +void OswAppWatchfaceMonotimer::drawWatch() { + OswHal* hal = OswHal::getInstance(); + + // hours + hal->gfx()->drawNTicks(120, 120, 117, 100, 12, ui->getForegroundColor()); + // 30 minutes + drawNShiftedTicks(hal->gfx(), 120, 120, 117, 105, 12, 360.0/24.0, ui->getForegroundColor()); + // 15 minutes + drawNShiftedTicks(hal->gfx(), 120, 120, 110, 105, 24, 360.0/48.0, ui->getForegroundColor()); + // 5 minutes + drawNShiftedMaskedTicks(hal->gfx(), 120, 120, 109, 108, 144, 0.0, 3, ui->getForegroundColor()); + + // hour labels + hal->gfx()->setTextSize(2); + hal->gfx()->setTextMiddleAligned(); + + static uint8_t positions[]= { + 155, 45, // 01 + 180, 75, // 02 + 200, 120, // 03 + 183, 165, // 04 + 155, 197, // 05 + 110, 210, // 06 + 65, 197, // 07 + 37, 165, // 08 + 23, 120, // 09 + 37, 75, // 10 + 65, 45, // 11 + 110, 30 // 12 + }; + + for (uint8_t i=0; i<12; ++i) { + hal->gfx()->setTextCursor(positions[i<<1], positions[(i<<1)|1]); + hal->gfx()->printDecimal(i+1, 2); + } + +#if OSW_PLATFORM_ENVIRONMENT_ACCELEROMETER == 1 + uint32_t steps = hal->environment->getStepsToday(); + uint32_t stepsTarget = OswConfigAllKeys::stepsPerDay.get(); + + hal->gfx()->setTextCenterAligned(); + hal->gfx()->setTextSize(1); + hal->gfx()->setTextCursor(120, 135); + hal->gfx()->setTextColor(steps>stepsTarget?ui->getSuccessColor():ui->getInfoColor()); + + hal->gfx()->print(steps); +#endif + + // ticks + uint32_t second = 0; + uint32_t minute = 0; + uint32_t hour = 0; + hal->getLocalTime(&hour, &minute, &second); + + if (OswConfigAllKeys::settingDisplayDualHourTick.get()) { + uint32_t dualSecond = 0; + uint32_t dualMinute = 0; + uint32_t dualHour = 0; + hal->getDualTime(&dualHour, &dualMinute, &dualSecond); + + hal->gfx()->drawThickTick(120, 120, 0, 105, (360.0 * (60 * dualHour + dualMinute)) / 720.0, 1, ui->getBackgroundDimmedColor()); + } + + hal->gfx()->drawThickTick(120, 120, 0, 105, (360.0 * (60 * hour + minute)) / 720.0, 1, ui->getForegroundColor()); + + hal->gfx()->fillEllipse(120, 120, 4, 4, ui->getForegroundColor()); +} + +#ifdef GIF_BG +OswAppGifPlayer* bgGif = new OswAppGifPlayer(); +#endif + +void OswAppWatchfaceMonotimer::setup() { +#ifdef GIF_BG + bgGif->setup(hal); +#endif +} + +void OswAppWatchfaceMonotimer::loop() { + OswHal* hal = OswHal::getInstance(); + if (hal->btnHasGoneDown(BUTTON_3)) { + hal->increaseBrightness(25); + } + if (hal->btnHasGoneDown(BUTTON_2)) { + hal->decreaseBrightness(25); + } + +#ifdef GIF_BG + // if (millis() - 1000 > lastDraw) { + bgGif->loop(hal); + // lastDraw = millis(); + // } +#endif + + drawWatch(); + hal->requestFlush(); +} + +void OswAppWatchfaceMonotimer::stop() { +#ifdef GIF_BG + bgGif->stop(); +#endif +} diff --git a/src/devices/bma400.cpp b/src/devices/bma400.cpp index 4cd899883..19273b539 100644 --- a/src/devices/bma400.cpp +++ b/src/devices/bma400.cpp @@ -1,3 +1,4 @@ +#ifndef OSW_EMULATOR #include #include #include @@ -226,7 +227,7 @@ void IRAM_ATTR isrTap() { #endif } -void OswDevices::BMA400::reset() { +void OswDevices::BMA400::resetStepCount() { int8_t rslt = bma400_soft_reset(&bma); bma400_check_rslt("bma400_soft_reset", rslt); step_count = 0; @@ -324,7 +325,7 @@ float OswDevices::BMA400::getAccelerationX() { #else return accelY; #endif -}; +} float OswDevices::BMA400::getAccelerationY() { #if defined(GPS_EDITION) @@ -334,11 +335,11 @@ float OswDevices::BMA400::getAccelerationY() { #else return accelX; #endif -}; +} float OswDevices::BMA400::getAccelerationZ() { return accelZ; -}; +} float OswDevices::BMA400::getTemperature() { int8_t rslt = BMA400_OK; @@ -350,7 +351,8 @@ float OswDevices::BMA400::getTemperature() { uint32_t OswDevices::BMA400::getStepCount() { return step_count; -}; +} uint8_t OswDevices::BMA400::getActivityMode() { return act_int; -}; \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/devices/bme280.cpp b/src/devices/bme280.cpp index 5edf9e92e..5b1767bc3 100644 --- a/src/devices/bme280.cpp +++ b/src/devices/bme280.cpp @@ -1,3 +1,4 @@ +#ifndef OSW_EMULATOR #include #include @@ -41,4 +42,5 @@ float OswDevices::BME280::getTemperature() { } float OswDevices::BME280::getHumidity() { return _hum; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/devices/ds3231mz.cpp b/src/devices/ds3231mz.cpp index f351bf1cc..fde6f80d3 100644 --- a/src/devices/ds3231mz.cpp +++ b/src/devices/ds3231mz.cpp @@ -1,3 +1,4 @@ +#ifndef OSW_EMULATOR #include #include @@ -72,4 +73,5 @@ float OswDevices::DS3231MZ::getTemperature() { if (Rtc.LastError()) return 0.0f; return rtcTemp.AsFloatDegC(); -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/devices/esp32.cpp b/src/devices/esp32.cpp index ac9c350db..2820a75ea 100644 --- a/src/devices/esp32.cpp +++ b/src/devices/esp32.cpp @@ -1,7 +1,9 @@ #include // The native ESP32 clock is wrapped by the standard time header #include #include +#ifndef OSW_EMULATOR #include +#endif #include #include @@ -20,6 +22,12 @@ uint8_t temprature_sens_read(); #endif uint8_t temprature_sens_read(); +#ifdef OSW_EMULATOR +uint8_t temprature_sens_read() { + return 128; +} +#endif + void OswDevices::NativeESP32::setup() { // Test temperature for 128 (sensor not available) for 10 times for(int i = 0; i < 10; i++) @@ -72,5 +80,9 @@ bool OswDevices::NativeESP32::isTemperatureSensorAvailable() { */ void OswDevices::NativeESP32::triggerNTPUpdate() { this->setUTCTime(0); +#ifndef OSW_EMULATOR configTime(OswConfigAllKeys::timeZone.get() * 3600 + 3600, OswConfigAllKeys::daylightOffset.get() * 3600, "pool.ntp.org", "time.nist.gov"); +#else + //TODO +#endif } \ No newline at end of file diff --git a/src/devices/qmc5883l.cpp b/src/devices/qmc5883l.cpp index 4bcb8b6a0..21ca8dfe3 100644 --- a/src/devices/qmc5883l.cpp +++ b/src/devices/qmc5883l.cpp @@ -1,3 +1,4 @@ +#ifndef OSW_EMULATOR #include #include @@ -51,4 +52,5 @@ int OswDevices::QMC5883L::getMagnetometerAzimuth() { byte OswDevices::QMC5883L::getMagnetometerBearing() { int a = getMagnetometerAzimuth(); return qmc5883l.getBearing(a); -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/src/gfx_util.cpp b/src/gfx_util.cpp new file mode 100644 index 000000000..425a5b9c5 --- /dev/null +++ b/src/gfx_util.cpp @@ -0,0 +1,172 @@ +#include "gfx_util.h" + +#include + +uint16_t rgb565(uint8_t red, uint8_t green, uint8_t blue) { + return ((red & 0b00011111000) << 8) | ((green & 0b00011111100) << 3) | (blue >> 3); +} + +uint32_t rgb888(uint8_t red, uint8_t green, uint8_t blue) { + return ((uint32_t)red << 16) | ((uint32_t)green << 8) | (uint32_t)blue; +} + +uint16_t rgb888to565(uint32_t rgb888) { + return rgb565(rgb888_red(rgb888), rgb888_green(rgb888), rgb888_blue(rgb888)); +} + +uint32_t rgb565to888(uint16_t rgb565) { + return rgb888(rgb565_red(rgb565), rgb565_green(rgb565), rgb565_blue(rgb565)); +} + +uint16_t blend(uint16_t target, uint16_t source, float alpha) { + uint8_t r = rgb565_red(source) * alpha + rgb565_red(target) * (1.0 - alpha); + uint8_t g = rgb565_green(source) * alpha + rgb565_green(target) * (1.0 - alpha); + uint8_t b = rgb565_blue(source) * alpha + rgb565_blue(target) * (1.0 - alpha); + + return rgb565(r, g, b); +} + +/** + * @brief Calculated the color code of a dimmed color + * + * @param oc Color code + * @param amount Amount to dimmed. + * @return uint16_t Color code of the dimmed color. + */ +uint16_t dimColor(uint16_t oc, uint8_t amount) { + uint16_t r = rgb565_red(oc); + uint16_t g = rgb565_green(oc); + uint16_t b = rgb565_blue(oc); + r = r >= amount ? r - amount : 0; + g = g >= amount * 2 ? g - amount * 2 : 0; + b = b >= amount ? b - amount : 0; + + uint16_t nc = rgb565(r, g, b); + return nc; +} + +uint16_t changeColor(uint16_t oc, float amount) { + uint16_t r = rgb565_red(oc); + uint16_t g = rgb565_green(oc); + uint16_t b = rgb565_blue(oc); + r = r * amount; + g = g * amount; + b = b * amount; + + uint16_t nc = rgb565(r, g, b); + return nc; +} + +uint8_t rgb565_red(uint16_t rgb565) { + // |rrrrrggg|gggbbbbb| + return (rgb565 >> 8) & 0b11111000; +} + +uint8_t rgb565_green(uint16_t rgb565) { + // |rrrrrggg|gggbbbbb| + return (rgb565 >> 3) & 0b11111100; +} + +uint8_t rgb565_blue(uint16_t rgb565) { + // |rrrrrggg|gggbbbbb| + return (rgb565 << 3); +} + +uint8_t rgb888_red(uint32_t rgb888) { + // |rrrrrrrr|gggggggg|bbbbbbbb| + return rgb888 >> 16; +} + +uint8_t rgb888_green(uint32_t rgb888) { + // |rrrrrrrr|gggggggg|bbbbbbbb| + return rgb888 >> 8; +} + +uint8_t rgb888_blue(uint32_t rgb888) { + // |rrrrrrrr|gggggggg|bbbbbbbb| + return rgb888; +} + +// Shamelessly copied from +// https://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both +void hsvToRgb(const unsigned char& h, const unsigned char& s, const unsigned char& v, unsigned char& r, + unsigned char& g, unsigned char& b) { + unsigned char region, remainder, p, q, t; + + if (s == 0) { + r = v; + g = v; + b = v; + return; + } + + region = h / 43; + remainder = (h - (region * 43)) * 6; + + p = (v * (255 - s)) >> 8; + q = (v * (255 - ((s * remainder) >> 8))) >> 8; + t = (v * (255 - ((s * (255 - remainder)) >> 8))) >> 8; + + switch (region) { + case 0: + r = v; + g = t; + b = p; + break; + case 1: + r = q; + g = v; + b = p; + break; + case 2: + r = p; + g = v; + b = t; + break; + case 3: + r = p; + g = q; + b = v; + break; + case 4: + r = t; + g = p; + b = v; + break; + default: + r = v; + g = p; + b = q; + break; + } +} + +// Also shamelessly copied from +// https://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both +void rgbToHsv(const unsigned char& r, const unsigned char& g, const unsigned char& b, unsigned char& h, + unsigned char& s, unsigned char& v) { + unsigned char rgbMin, rgbMax; + + rgbMin = r < g ? (r < b ? r : b) : (g < b ? g : b); + rgbMax = r > g ? (r > b ? r : b) : (g > b ? g : b); + + v = rgbMax; + if (v == 0) { + h = 0; + s = 0; + return; + } + + s = 255 * long(rgbMax - rgbMin) / v; + if (s == 0) { + h = 0; + return; + } + + if (rgbMax == r) + h = 0 + 43 * (g - b) / (rgbMax - rgbMin); + else if (rgbMax == g) + h = 85 + 43 * (b - r) / (rgbMax - rgbMin); + else + h = 171 + 43 * (r - g) / (rgbMax - rgbMin); +}; diff --git a/src/hal/Arduino_Canvas_Graphics2D.cpp b/src/hal/Arduino_Canvas_Graphics2D.cpp index 90d223450..65dfba64c 100644 --- a/src/hal/Arduino_Canvas_Graphics2D.cpp +++ b/src/hal/Arduino_Canvas_Graphics2D.cpp @@ -1,41 +1,36 @@ #include "Arduino_Canvas_Graphics2D.h" +#ifndef OSW_EMULATOR #include -#include +#endif #include #include "config_defaults.h" Arduino_Canvas_Graphics2D::Arduino_Canvas_Graphics2D(int16_t w, int16_t h, Arduino_G* output, int16_t output_x, int16_t output_y) - : Arduino_GFX(w, h), _output(output), _output_x(output_x), _output_y(output_y) {} + : Graphics2DPrint(w, h, DISP_CHUNK_H, true), _output(output), _output_x(output_x), _output_y(output_y) {} void Arduino_Canvas_Graphics2D::begin(int32_t speed) { - _gfx2d = new Graphics2DPrint(_width, _height, DISP_CHUNK_H, true); _output->begin(speed); // _output->fillScreen(BLACK); } void Arduino_Canvas_Graphics2D::writePixelPreclipped(int16_t x, int16_t y, uint16_t color) { - _gfx2d->drawPixel(x, y, color); + this->drawPixel(x, y, color); } void Arduino_Canvas_Graphics2D::writeFastVLine(int16_t x, int16_t y, int16_t h, uint16_t color) { - _gfx2d->drawVLine(x, y, h, color); + this->drawVLine(x, y, h, color); } void Arduino_Canvas_Graphics2D::writeFastHLine(int16_t x, int16_t y, int16_t w, uint16_t color) { - _gfx2d->drawHLine(x, y, w, color); + this->drawHLine(x, y, w, color); } -void Arduino_Canvas_Graphics2D::flush(void) { +void Arduino_Canvas_Graphics2D::flush() { // only flush if there is a buffer - if (_gfx2d->hasBuffer()) { - uint8_t chunkHeight = _gfx2d->getChunkHeight(); - for (uint8_t chunk = 0; chunk < _gfx2d->numChunks(); chunk++) { - _output->draw16bitRGBBitmap(_gfx2d->getChunkOffset(chunk), chunk * chunkHeight, _gfx2d->getChunk(chunk), - _gfx2d->getChunkWidth(chunk), chunkHeight); + if (this->hasBuffer()) { + uint8_t chunkHeight = this->getChunkHeight(); + for (uint8_t chunk = 0; chunk < this->numChunks(); chunk++) { + _output->draw16bitRGBBitmap(this->getChunkOffset(chunk), chunk * chunkHeight, this->getChunk(chunk), + this->getChunkWidth(chunk), chunkHeight); } } - -} - -Graphics2DPrint* Arduino_Canvas_Graphics2D::getGraphics2D(void) { - return _gfx2d; } diff --git a/src/hal/buttons.cpp b/src/hal/buttons.cpp index fd6bd9579..a082e9130 100644 --- a/src/hal/buttons.cpp +++ b/src/hal/buttons.cpp @@ -1,16 +1,12 @@ - +#ifndef OSW_EMULATOR #include "driver/rtc_io.h" +#endif #include "osw_hal.h" #include "osw_pins.h" // assign pins to buttons uint8_t buttonPins[] = {BTN_1, BTN_2, BTN_3}; // see osw_pins.h -// assign active LOW or HIGH states according to hardware -#if defined(GPS_EDITION_ROTATED) -uint8_t buttonClickStates[] = {HIGH, HIGH, LOW}; -#else -uint8_t buttonClickStates[] = {LOW, HIGH, HIGH}; -#endif +uint8_t buttonClickStates[] = BTN_STATE_ARRAY; // Graphics2D screenBuffer(DISP_W, DISP_H, DISP_CHUNK_H); diff --git a/src/hal/devices.cpp b/src/hal/devices.cpp index ade7fe0df..4e285fd7e 100644 --- a/src/hal/devices.cpp +++ b/src/hal/devices.cpp @@ -16,7 +16,7 @@ OswHal::Devices::Devices() { this->ds3231mz = new OswDevices::DS3231MZ(); #endif #if OSW_PLATFORM_HARDWARE_VIRTUAL == 1 - this->virtualDevice = new OswDevices::Virtual(10); + this->virtualDevice = new OswDevices::Virtual(100); #endif this->esp32 = new OswDevices::NativeESP32(); } diff --git a/src/hal/display.cpp b/src/hal/display.cpp index 6a6ce410a..4b0609be2 100644 --- a/src/hal/display.cpp +++ b/src/hal/display.cpp @@ -1,6 +1,8 @@ #include +#ifndef OSW_EMULATOR #include #include +#endif #include #include #include @@ -11,13 +13,16 @@ #include "osw_hal.h" #include "osw_pins.h" +#ifndef OSW_EMULATOR Arduino_DataBus* bus = new Arduino_ESP32SPI(TFT_DC, TFT_CS, TFT_SCK, TFT_MOSI, TFT_MISO, VSPI /* spi_num */); #if defined(GPS_EDITION_ROTATED) Arduino_GC9A01* tft = new Arduino_GC9A01(bus, TFT_RST, 1 /* rotation */, true /* IPS */); #else Arduino_GC9A01* tft = new Arduino_GC9A01(bus, TFT_RST, 0 /* rotation */, true /* IPS */); #endif -Arduino_Canvas_Graphics2D* canvas = new Arduino_Canvas_Graphics2D(DISP_W, DISP_H, tft); +#else +FakeDisplay* tft = nullptr; +#endif class PixelPainter : public DrawPixel { public: @@ -38,15 +43,15 @@ void OswHal::requestEnableDisplayBuffer() { void OswHal::disableDisplayBuffer() { if(!this->displayBufferEnabled()) return; - canvas->getGraphics2D()->disableBuffer(pixelPainter); + this->canvas->disableBuffer(pixelPainter); } void OswHal::enableDisplayBuffer() { if(this->displayBufferEnabled()) return; - canvas->getGraphics2D()->enableBuffer(); + this->canvas->enableBuffer(); } bool OswHal::displayBufferEnabled() { - return canvas->getGraphics2D()->hasBuffer(); + return this->canvas->hasBuffer(); } void OswHal::setupDisplay() { @@ -56,20 +61,25 @@ void OswHal::setupDisplay() { ledcSetup(1, 12000, 8); // 12 kHz PWM, 8-bit resolution ledcWrite(1, 0); #endif +#ifdef OSW_EMULATOR + if(!tft) + tft = fakeDisplayInstance.get(); +#endif + + // Moved from static allocation to here, as new() operators are limited (size-wise) in that context + if(!this->canvas) + this->canvas = new Arduino_Canvas_Graphics2D(DISP_W, DISP_H, tft); - canvas->begin(0); + this->canvas->begin(0); tft->displayOn(); _screenOnSince = millis(); } -Arduino_TFT* OswHal::getArduino_TFT(void) { - return tft; -} Arduino_Canvas_Graphics2D* OswHal::getCanvas(void) { - return canvas; + return this->canvas; } Graphics2DPrint* OswHal::gfx(void) { - return canvas->getGraphics2D(); + return this->canvas; } void OswHal::requestFlush(void) { @@ -81,7 +91,7 @@ bool OswHal::isRequestFlush(void) { void OswHal::flushCanvas(void) { _requestFlush = false; - canvas->flush(); + this->canvas->flush(); } void OswHal::displayOff(void) { @@ -119,7 +129,7 @@ void OswHal::setBrightness(uint8_t b) { OswConfigAllKeys::settingDisplayBrightness.set(_brightness); OswConfig::getInstance()->disableWrite(); #else - digitalWrite(TFT_LED, brightness); + digitalWrite(TFT_LED, _brightness); #endif #ifndef NDEBUG Serial.println("Setting brightness to " + String(b)); @@ -139,7 +149,7 @@ void OswHal::increaseBrightness(uint8_t v) { new_brightness = _brightness + v; } setBrightness(new_brightness); -}; +} void OswHal::decreaseBrightness(uint8_t v) { uint8_t new_brightness = 0; @@ -162,7 +172,7 @@ void OswHal::decreaseBrightness(uint8_t v) { new_brightness = _brightness - v; } setBrightness(new_brightness); -}; +} uint8_t OswHal::screenBrightness(bool checkHardware) { uint8_t screen_brightness = 0; diff --git a/src/hal/environment.cpp b/src/hal/environment.cpp index a38295765..c46f38c88 100644 --- a/src/hal/environment.cpp +++ b/src/hal/environment.cpp @@ -1,4 +1,7 @@ #include +#ifdef OSW_EMULATOR +#include +#endif #include #include @@ -115,12 +118,7 @@ void OswHal::Environment::setupStepStatistics() { const uint32_t currentSteps = this->getStepsToday(); this->_stepsCache[lastDoW] = currentSteps; // write current step to last dow this->_stepsSum += currentSteps; // Let's just hope this never rolls over... -#if OSW_PLATFORM_HARDWARE_BMA400 == 1 - if(OswHal::getInstance()->devices->bma400 == this->accelSensor) - OswHal::getInstance()->devices->bma400->reset(); -#else -#warning "Are you sure your acceleration provider does not need to be reset?" -#endif + OswHal::getInstance()->environment->resetStepCount(); if(OswConfigAllKeys::stepsHistoryClear.get()) { if(currDoW > lastDoW) { // set stepscache to 0 in ]lastDoW, currDoW[ @@ -154,7 +152,7 @@ void OswHal::Environment::setupStepStatistics() { } prefs.end(); #ifndef NDEBUG - Serial.print(String(__FILE__) + ": Current step history (day " + String(currDoW) + ", today " + String(OswHal::getInstance()->devices->bma400->getStepCount()) + ", sum " + String(this->_stepsSum) + ") is: {"); + Serial.print(String(__FILE__) + ": Current step history (day " + String(currDoW) + ", today " + String(OswHal::getInstance()->environment->getStepsToday()) + ", sum " + String(this->_stepsSum) + ") is: {"); for(size_t i = 0; i < 7; i++) { if(i > 0) Serial.print(", "); @@ -175,6 +173,12 @@ uint32_t OswHal::Environment::getStepsToday() { return this->accelSensor->getStepCount(); } +void OswHal::Environment::resetStepCount() { + if(!this->accelSensor) + throw std::runtime_error("No acceleration provider!"); + return this->accelSensor->resetStepCount(); +} + #ifdef OSW_FEATURE_STATS_STEPS uint32_t OswHal::Environment::getStepsOnDay(uint8_t dayOfWeek, bool lastWeek) { uint32_t day = 0; diff --git a/src/hal/esp32/sd_filesystem.cpp b/src/hal/esp32/sd_filesystem.cpp index 4e7d32029..055173a2f 100644 --- a/src/hal/esp32/sd_filesystem.cpp +++ b/src/hal/esp32/sd_filesystem.cpp @@ -1,3 +1,4 @@ +#ifndef OSW_EMULATOR #include "hal/esp32/sd_filesystem.h" #include @@ -33,3 +34,4 @@ bool SDFileSystemHal::initialize() { return true; } } +#endif \ No newline at end of file diff --git a/src/hal/esp32/spiffs_filesystem.cpp b/src/hal/esp32/spiffs_filesystem.cpp index 14b39d881..84fd68d5f 100644 --- a/src/hal/esp32/spiffs_filesystem.cpp +++ b/src/hal/esp32/spiffs_filesystem.cpp @@ -1,6 +1,8 @@ +#ifndef OSW_EMULATOR #include "hal/esp32/spiffs_filesystem.h" bool SPIFFSFileSystemHal::initialize() { // Mount the filesystem and register vfs return SPIFFS.begin(true, FS_MOUNT_POINT); } +#endif \ No newline at end of file diff --git a/src/hal/gps.cpp b/src/hal/gps.cpp index 4875c9d34..4343579c6 100644 --- a/src/hal/gps.cpp +++ b/src/hal/gps.cpp @@ -1,3 +1,4 @@ +#ifndef OSW_EMULATOR #include #include #define DEBUG_PORT Serial // default for most sketches @@ -103,5 +104,5 @@ void OswHal::gpsParse(void) { HardwareSerial OswHal::getSerialGPS(void) { return SerialGPS; } - #endif +#endif \ No newline at end of file diff --git a/src/hal/power.cpp b/src/hal/power.cpp index 91c88f06d..c64128b34 100644 --- a/src/hal/power.cpp +++ b/src/hal/power.cpp @@ -1,6 +1,8 @@ #include +#ifndef OSW_EMULATOR #include "driver/rtc_io.h" +#endif #include "osw_hal.h" #include "osw_pins.h" @@ -55,7 +57,7 @@ void OswHal::updatePowerStatistics(uint16_t currBattery) { } } -boolean OswHal::isCharging(void) { +bool OswHal::isCharging(void) { return digitalRead(OSW_DEVICE_TPS2115A_STATPWR); // != 0 means there is V(IN2) in use } diff --git a/src/hal/sd.cpp b/src/hal/sd.cpp index 180136240..e8f315947 100644 --- a/src/hal/sd.cpp +++ b/src/hal/sd.cpp @@ -1,3 +1,4 @@ +#ifndef OSW_EMULATOR #define FS_NO_GLOBALS #include #include @@ -124,3 +125,4 @@ void OswHal::sdOff(void) { } #endif +#endif \ No newline at end of file diff --git a/src/hal/time.cpp b/src/hal/time.cpp index e081cb685..bd44163d0 100644 --- a/src/hal/time.cpp +++ b/src/hal/time.cpp @@ -24,7 +24,7 @@ void OswHal::updateTimeProvider() { } #ifndef NDEBUG if(!this->timeProvider) - Serial.println(String(__FILE__) + ": Temperature API enabled, but no provider available!"); + Serial.println(String(__FILE__) + ": No provider for Time is available!"); #endif } diff --git a/src/main.cpp b/src/main.cpp index 063e493ba..320b170ad 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include @@ -8,7 +8,6 @@ #include #include #include -#include #include //randomSeed #include //time @@ -38,7 +37,7 @@ #include "./apps/tools/OswAppPrintDebug.h" #endif #include "./apps/tools/OswAppTimeConfig.h" -#include "./apps/tools/water_level.h" +#include "./apps/tools/OswAppWaterLevel.h" #include "./apps/tools/OswAppFitnessStats.h" #ifdef OSW_FEATURE_STATS_STEPS #include "./apps/tools/OswAppKcalStats.h" @@ -51,6 +50,7 @@ #include "./apps/watchfaces/OswAppWatchfaceDual.h" #include "./apps/watchfaces/OswAppWatchfaceFitness.h" #include "./apps/watchfaces/OswAppWatchfaceBinary.h" +#include "./apps/watchfaces/OswAppWatchfaceMonotimer.h" #if OSW_PLATFORM_ENVIRONMENT_MAGNETOMETER == 1 && OSW_PLATFORM_HARDWARE_QMC5883L == 1 #include "./apps/_experiments/magnetometer_calibrate.h" #endif @@ -58,7 +58,6 @@ #include "./apps/main/map.h" #endif #include "./services/OswServiceTaskBLECompanion.h" -#include "debug_scani2c.h" #include "services/OswServiceTaskMemMonitor.h" #include "services/OswServiceTasks.h" #ifdef OSW_FEATURE_WIFI @@ -86,7 +85,7 @@ OswAppSwitcher fitnessAppSwitcher(BUTTON_1, SHORT_PRESS, false, false, &fitnessA void setup() { Serial.begin(115200); - Serial.println(String("Welcome to the OSW-OS! This build is based on commit ") + GIT_COMMIT_HASH +" from " + GIT_BRANCH_NAME + + Serial.println(String("Welcome to the OSW-OS! This build is based on commit ") + GIT_COMMIT_HASH + " from " + GIT_BRANCH_NAME + ". Compiled at " + __DATE__ + " " + __TIME__ + " for platform " + PIO_ENV_NAME + "."); hal = OswHal::getInstance(); @@ -110,6 +109,7 @@ void setup() { watchFaceSwitcher.registerApp(new OswAppWatchfaceDual()); watchFaceSwitcher.registerApp(new OswAppWatchfaceFitness()); watchFaceSwitcher.registerApp(new OswAppWatchfaceBinary()); + watchFaceSwitcher.registerApp(new OswAppWatchfaceMonotimer()); mainAppSwitcher.registerApp(&watchFaceSwitcher); mainAppSwitcher.setup(); @@ -126,7 +126,7 @@ void setup() { void loop() { static time_t lastPowerUpdate = time(nullptr) + 2; // We consider a run of at least 2 seconds as "success" - static boolean delayedAppInit = true; + static bool delayedAppInit = true; // check possible interaction with ULP program #if USE_ULP == 1 @@ -221,7 +221,7 @@ void loop() { #endif } -#ifndef NDEBUG +#ifndef OSW_EMULATOR OswServiceAllTasks::memory.updateLoopTaskStats(); #endif } diff --git a/src/math_angles.cpp b/src/math_angles.cpp new file mode 100644 index 000000000..740943fa1 --- /dev/null +++ b/src/math_angles.cpp @@ -0,0 +1,63 @@ +#include "math_angles.h" + +#include + +// rotate a point around a center (cy,cy), with a radius r, 0 degrees ist 12 o'clock +float rpx(float cx, float r, float d) { + return cx + r * cos((d - 90) * 1000.0 / 57296.0); +} +float rpy(float cy, float r, float d) { + return cy + r * sin((d - 90) * 1000.0 / 57296.0); +} + +// rotate a point around a point +int32_t rotateX(int32_t x, int32_t y, int32_t rx, int32_t ry, float cosA, float sinA) { + return (x - rx) * cosA + (y - ry) * sinA; +} +int32_t rotateY(int32_t x, int32_t y, int32_t rx, int32_t ry, float cosA, float sinA) { + return (y - ry) * cosA - (x - rx) * sinA; +} + +int32_t rotateX(int32_t x, int32_t y, int32_t rx, int32_t ry, float a) { + return (x - rx) * cos(a) + (y - ry) * sin(a); +} +int32_t rotateY(int32_t x, int32_t y, int32_t rx, int32_t ry, float a) { + return (y - ry) * cos(a) - (x - rx) * sin(a); +} + + +// seconds to degrees (0-360) +float s2d(long seconds) { + return (seconds % 60) * 6; +} + +// minutes to degrees (0-360) +float m2d(long seconds) { + long fh = (seconds / 3600); // full hours + return (((seconds - fh * 3600) / 60.0)) * 6; +} + +// hours to degrees (0-360) +float h2d(long seconds) { + long fd = (seconds / 3600) / 24; // full days + return ((seconds - fd * 24 * 3600) / 3600.0) * 30; +} + +float sign(float x1, float y1, float x2, float y2, float x3, float y3) { + return (x1 - x3) * (y2 - y3) - (x2 - x3) * (y1 - y3); +} + +// Source: https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle +bool pointInsideTriangle(float px, float py, float x1, float y1, float x2, float y2, float x3, float y3) { + float d1, d2, d3; + bool has_neg, has_pos; + + d1 = sign(px, py, x1, y1, x2, y2); + d2 = sign(px, py, x2, y2, x3, y3); + d3 = sign(px, py, x3, y3, x1, y1); + + has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0); + has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0); + + return !(has_neg && has_pos); +} \ No newline at end of file diff --git a/src/math_osm.cpp b/src/math_osm.cpp new file mode 100644 index 000000000..ac65ab18b --- /dev/null +++ b/src/math_osm.cpp @@ -0,0 +1,39 @@ +#include "math_osm.h" + +#include + +// source: https://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#C.2FC.2B.2B + +// we return float here, because we need the fraction + +float lon2tilex(float lon, uint8_t z) { + return (lon + 180.0) / 360.0 * (float)(1 << z); +} + +float lat2tiley(float lat, uint8_t z) { + float latrad = lat * PI / 180.0; + return (1.0 - asinh(tan(latrad)) / PI) / 2.0 * (float)(1 << z); +} + +// helper function to get the offset within the tile +int32_t tileOffset(float tilex) { + int32_t decimalPlaces = (int32_t)tilex; + return (int32_t)(255 * (tilex - decimalPlaces)); +} + +float tilex2lon(float x, uint8_t z) { + return x / (float)(1 << z) * 360.0 - 180; +} + +float tiley2lat(float y, uint8_t z) { + float n = PI - TWO_PI * y / (float)(1 << z); + return 180.0 / PI * atan(0.5 * (exp(n) - exp(-n))); +} + +float osmResolution[] = {156543.03, 78271.52, 39135.76, 19567.88, 9783.94, 4891.97, 2445.98, 1222.99, 611.50, 305.75, + 152.87, 76.43, 38.21, 19.10, 9.55, 4.77, 2.38, 1.19, 0.59 + }; + +float getTileResolution(float lat, uint8_t z) { + return 156543.03 /*meters/pixel*/ * cos(lat) / (2 ^ z); +} diff --git a/src/osm_render.cpp b/src/osm_render.cpp new file mode 100644 index 000000000..8f6022320 --- /dev/null +++ b/src/osm_render.cpp @@ -0,0 +1,115 @@ +#include "osm_render.h" + +#include "gfx_2d.h" + +// #include + +// Memory estimation in bytes: +// 256*256*2=131.072 +// 2x2 = 524.288 +// 3x3 = 1.179.648 +// 4x4 = 2.097.152 +// 5x5 = 3.276.800 + +Graphics2D* getTile(BufferedTile** buffer, uint8_t bufferLength, loadTile loadTileFn, uint32_t tileX, uint32_t tileY, + uint8_t tileZ) { + // return buffered tile + for (uint16_t i = 0; i < bufferLength; i++) { + if (buffer[i] != NULL && buffer[i]->hasTile(tileX, tileY, tileZ)) { + return buffer[i]->getGraphics(); + } + } + + // find oldest tile + unsigned long oldestTimeStamp = 4294967295; + uint16_t oldestIndex = 0; + for (uint16_t i = 0; i < bufferLength; i++) { + if (buffer[i] != NULL && buffer[i]->getLastUsed() < oldestTimeStamp) { + oldestTimeStamp = buffer[i]->getLastUsed(); + oldestIndex = i; + } + } + + // overwrite withe new tile + buffer[oldestIndex]->loadTile(loadTileFn, tileX, tileY, tileZ); + + // return fresh tile + return buffer[oldestIndex]->getGraphics(); +} + +void drawTilesBuffered(BufferedTile** buffer, uint8_t n, Graphics2D* target, // + loadTile loadTileFn, float lat, float lon, uint8_t z) { + // TODO: proxy loadTileFn and reuse buffered tile if present + float tileX = lon2tilex(lon, z); + float tileY = lat2tiley(lat, z); + uint16_t zoom = z; + + int32_t tposX = target->getWidth() / 2 - tileOffset(tileX); + int32_t tposY = target->getHeight() / 2 - tileOffset(tileY); + + // getTile(buffer, n, loadTileFn, tileX, tileY, zoom)->fillFrame(0, 0, 255, 255, rgb565(255, 0, 0)); + + target->drawGraphics2D(tposX, tposY, getTile(buffer, n, loadTileFn, tileX, tileY, zoom)); + + // TODO below is not optimal, we have cases where nothing needs to be drawn + if (tileOffset(tileX) < 128 && tileOffset(tileY) < 128) { + // top left (first tile is bot right) + target->drawGraphics2D(tposX - TILE_W, tposY /* */, getTile(buffer, n, loadTileFn, tileX - 1, tileY, zoom)); + target->drawGraphics2D(tposX - TILE_W, tposY - TILE_H, getTile(buffer, n, loadTileFn, tileX - 1, tileY - 1, zoom)); + target->drawGraphics2D(tposX /* */, tposY - TILE_H, getTile(buffer, n, loadTileFn, tileX, tileY - 1, zoom)); + } else if (tileOffset(tileX) < 128 && tileOffset(tileY) >= 128) { + // bot left (first tile is top right) + target->drawGraphics2D(tposX - TILE_W, tposY /* */, getTile(buffer, n, loadTileFn, tileX - 1, tileY, zoom)); + target->drawGraphics2D(tposX - TILE_W, tposY + TILE_H, getTile(buffer, n, loadTileFn, tileX - 1, tileY + 1, zoom)); + target->drawGraphics2D(tposX /* */, tposY + TILE_H, getTile(buffer, n, loadTileFn, tileX, tileY + 1, zoom)); + } else if (tileOffset(tileX) >= 128 && tileOffset(tileY) >= 128) { + // bot right (first tile is top left) + target->drawGraphics2D(tposX /* */, tposY + TILE_H, getTile(buffer, n, loadTileFn, tileX, tileY + 1, zoom)); + target->drawGraphics2D(tposX + TILE_W, tposY + TILE_H, getTile(buffer, n, loadTileFn, tileX + 1, tileY + 1, zoom)); + target->drawGraphics2D(tposX + TILE_W, tposY /* */, getTile(buffer, n, loadTileFn, tileX + 1, tileY, zoom)); + } else { + // top right (first tile is bot left) + target->drawGraphics2D(tposX + TILE_W, tposY /* */, getTile(buffer, n, loadTileFn, tileX + 1, tileY, zoom)); + target->drawGraphics2D(tposX + TILE_W, tposY - TILE_H, getTile(buffer, n, loadTileFn, tileX + 1, tileY - 1, zoom)); + target->drawGraphics2D(tposX /* */, tposY - TILE_H, getTile(buffer, n, loadTileFn, tileX, tileY - 1, zoom)); + } +} + +void drawTiles(Graphics2D* target, loadTile loadTileFn, float lat, float lon, uint8_t z) { + float tileX = lon2tilex(lon, z); + float tileY = lat2tiley(lat, z); + uint16_t zoom = z; + + int32_t tposX = target->getWidth() / 2 - tileOffset(tileX); + int32_t tposY = target->getHeight() / 2 - tileOffset(tileY); + // target->fill(rgb565(0, 0, 0)); + + loadTileFn(target, zoom, tileX, tileY, tposX, tposY); + // target->drawFrame(tposX, tposY, 256, 256, rgb565(255, 0, 0)); + // TODO below is not optimal, we have cases where nothing needs to be drawn + if (tileOffset(tileX) < 128 && tileOffset(tileY) < 128) { + // top left (first tile is bot right) + loadTileFn(target, zoom, tileX - 1, tileY, tposX - TILE_W, tposY); + loadTileFn(target, zoom, tileX - 1, tileY - 1, tposX - TILE_W, tposY - TILE_H); + loadTileFn(target, zoom, tileX, tileY - 1, tposX, tposY - TILE_H); + // target->drawFrame(200, 200, 10, 10, rgb565(255, 0, 0)); + } else if (tileOffset(tileX) < 128 && tileOffset(tileY) >= 128) { + // bot left (first tile is top right) + loadTileFn(target, zoom, tileX - 1, tileY, tposX - TILE_W, tposY); + loadTileFn(target, zoom, tileX - 1, tileY + 1, tposX - TILE_W, tposY + TILE_H); + loadTileFn(target, zoom, tileX, tileY + 1, tposX, tposY + TILE_H); + // target->drawFrame(200, 40, 10, 10, rgb565(255, 0, 0)); + } else if (tileOffset(tileX) >= 128 && tileOffset(tileY) >= 128) { + // bot right (first tile is top left) + loadTileFn(target, zoom, tileX, tileY + 1, tposX, tposY + TILE_H); + loadTileFn(target, zoom, tileX + 1, tileY + 1, tposX + TILE_W, tposY + TILE_H); + loadTileFn(target, zoom, tileX + 1, tileY, tposX + TILE_W, tposY); + // target->drawFrame(40, 40, 10, 10, rgb565(255, 0, 0)); + } else { + // top right (first tile is bot left) + loadTileFn(target, zoom, tileX + 1, tileY, tposX + TILE_W, tposY); + loadTileFn(target, zoom, tileX + 1, tileY - 1, tposX + TILE_W, tposY - TILE_H); + loadTileFn(target, zoom, tileX, tileY - 1, tposX, tposY - TILE_H); + // target->drawFrame(40, 200, 10, 10, rgb565(255, 0, 0)); + } +} diff --git a/src/osw_config.cpp b/src/osw_config.cpp index 7bfc6c942..c7c0f61d8 100644 --- a/src/osw_config.cpp +++ b/src/osw_config.cpp @@ -1,7 +1,11 @@ +#include + #include "osw_config.h" #include +#ifndef OSW_EMULATOR #include +#endif #include #include "osw_config_keys.h" @@ -104,7 +108,8 @@ String OswConfig::getConfigJSON() { config["entries"][i]["label"] = key->label; if(key->help) config["entries"][i]["help"] = key->help; - config["entries"][i]["type"] = key->type; + char typeBuffer[2] = {(char)(key->type), '\0'}; + config["entries"][i]["type"] = (char*) typeBuffer; // The type is "OswConfigKeyTypedUIType", so we have to create a char* as ArduinoJSON takes these (only char*!) in as a copy config["entries"][i]["default"] = key->toDefaultString(); config["entries"][i]["value"] = key->toString(); } @@ -138,7 +143,7 @@ void OswConfig::parseDataJSON(const char* json) { break; } if (!key) { - Serial.println("WARNING: Unknown key id \"" + String(entryId) + "\" provided -> ignoring..."); + Serial.println("WARNING: Unknown key id \"" + entryId + "\" provided -> ignoring..."); continue; } #ifndef NDEBUG @@ -154,7 +159,11 @@ void OswConfig::parseDataJSON(const char* json) { #endif } + this->notifyChange(); +} + +void OswConfig::notifyChange() { // Reload parts of the OS, which buffer values OswUI::getInstance()->resetTextColors(); OswAppWatchfaceDigital::refreshDateFormatCache(); -} +} \ No newline at end of file diff --git a/src/osw_config_keys.cpp b/src/osw_config_keys.cpp index 2b53df824..cb191cf71 100644 --- a/src/osw_config_keys.cpp +++ b/src/osw_config_keys.cpp @@ -1,7 +1,9 @@ #include "osw_config_keys.h" #include +#ifndef OSW_EMULATOR #include +#endif #include "gfx_util.h" #include "osw_util.h" @@ -21,11 +23,14 @@ namespace OswConfigAllKeys { // TODO Translate all this? #ifdef OSW_FEATURE_WIFI OswConfigKeyString hostname("i", "WiFi", "Hostname", "Used e.g. for the wifi station", DEVICE_NAME); +OswConfigKeyBool hostPasswordEnabled("i3", "WiFi", "Enable AutoAP Password", nullptr, true); +OswConfigKeyPassword hostPass("i2", "WiFi", "AutoAP Password", "Password to use for AutoAP (leave empty to use random)", ""); + #ifdef OSW_FEATURE_WIFI_ONBOOT OswConfigKeyBool wifiBootEnabled("j", "WiFi", "Enable on boot", "This will drain your battery faster!", WIFI_ON_BOOT); #endif OswConfigKeyBool wifiAlwaysNTPEnabled("k", "WiFi", "Always fetch time (when connected)", nullptr, NTP_ALWAYS_ON_WIFI); -OswConfigKeyBool wifiAutoAP("l", "WiFi", "Enable Auto AP", +OswConfigKeyBool wifiAutoAP("l", "WiFi", "Enable AutoAP", "When the connection to the wifi fails, just create an own wifi station.", WIFI_AUTO_AP); OswConfigKeyString wifiSsid("a", "WiFi", "1st SSID", "Your wifi name", CONFIG_WIFI_SSID); OswConfigKeyPassword wifiPass("b", "WiFi", "1st Password", nullptr, CONFIG_WIFI_PASS); @@ -42,7 +47,7 @@ OswConfigKeyShort settingDisplayTimeout("s2", "Display", "Display Timeout", OswConfigKeyBool settingDisplayOverlays("s3", "Display", "Display Overlays", "Show overlays at all", DISPLAY_OVERLAYS); OswConfigKeyBool settingDisplayOverlaysOnWatchScreen("s4", "Display", "Display Watchface Overlays", nullptr, DISPLAY_OVERLAYS_ON_WF); OswConfigKeyDropDown settingDisplayDefaultWatchface("n", "Display", - "Default Watchface ID (analog, digital, mix, Dual-time, Fitness-tracking, binary)", "0,1,2,3,4,5", String(CONFIG_DEFAULT_WATCHFACE_INDEX)); + "Default Watchface ID (analog, digital, mix, Dual-time, Fitness-tracking, binary, monotimer)", "0,1,2,3,4,5,6", String(CONFIG_DEFAULT_WATCHFACE_INDEX)); OswConfigKeyBool settingDisplayDualHourTick("h2", "Display", "Display Dual-Time Hour Tick", "Show dual time hour tick", false); #if OSW_PLATFORM_ENVIRONMENT_ACCELEROMETER == 1 OswConfigKeyBool settingDisplayStepsGoal("g1", "Display", "Display Steps Goal", "Show goal steps", true); @@ -96,13 +101,15 @@ OswConfigKeyBool stepsHistoryClear("o", "Fitness", "Clear historical days", "In OswConfigKey* oswConfigKeys[] = { #ifdef OSW_FEATURE_WIFI // wifi - &OswConfigAllKeys::hostname, &OswConfigAllKeys::wifiSsid, &OswConfigAllKeys::wifiPass, + &OswConfigAllKeys::hostname, + &OswConfigAllKeys::wifiSsid, &OswConfigAllKeys::wifiPass, &OswConfigAllKeys::fallbackWifiSsid1st,&OswConfigAllKeys::fallbackWifiPass1st, &OswConfigAllKeys::fallbackWifiSsid2nd,&OswConfigAllKeys::fallbackWifiPass2nd, #ifdef OSW_FEATURE_WIFI_ONBOOT & OswConfigAllKeys::wifiBootEnabled, #endif & OswConfigAllKeys::wifiAlwaysNTPEnabled, &OswConfigAllKeys::wifiAutoAP, + &OswConfigAllKeys::hostPasswordEnabled, &OswConfigAllKeys::hostPass, #endif // display &OswConfigAllKeys::settingDisplayTimeout, &OswConfigAllKeys::settingDisplayBrightness, diff --git a/src/osw_hal.cpp b/src/osw_hal.cpp index b9a4e3ddc..a62ca3f62 100644 --- a/src/osw_hal.cpp +++ b/src/osw_hal.cpp @@ -1,25 +1,41 @@ #include #include +#ifndef OSW_EMULATOR #if defined(GPS_EDITION) || defined(GPS_EDITION_ROTATED) #include "hal/esp32/sd_filesystem.h" #else #include "hal/esp32/spiffs_filesystem.h" #endif +#endif + +OswHal* OswHal::instance = nullptr; +OswHal* OswHal::getInstance() { + if(OswHal::instance == nullptr) { +#ifndef OSW_EMULATOR #if defined(GPS_EDITION) || defined(GPS_EDITION_ROTATED) -OswHal* OswHal::instance = new OswHal(new SDFileSystemHal()); + OswHal::instance = new OswHal(new SDFileSystemHal()); #else -OswHal* OswHal::instance = new OswHal(new SPIFFSFileSystemHal()); + OswHal::instance = new OswHal(new SPIFFSFileSystemHal()); #endif - -OswHal* OswHal::getInstance() { +#else + OswHal::instance = new OswHal(nullptr); +#endif + } return OswHal::instance; }; +void OswHal::resetInstance() { + delete OswHal::instance; + OswHal::instance = nullptr; // On the next access it will be recreated! +} + OswHal::OswHal(FileSystemHal* fs) : fileSystem(fs) { //begin I2c communication +#ifndef OSW_EMULATOR Wire.begin(OSW_DEVICE_I2C_SDA, OSW_DEVICE_I2C_SCL, 100000L); +#endif } OswHal::~OswHal() { diff --git a/src/osw_ui.cpp b/src/osw_ui.cpp index 58fda2ecb..4393c8949 100644 --- a/src/osw_ui.cpp +++ b/src/osw_ui.cpp @@ -95,11 +95,11 @@ void OswUI::loop(OswAppSwitcher& mainAppSwitcher, uint16_t& mainAppIndex) { } // Limit to configured fps and handle display flushing - if (millis() - lastFlush > 1000 / this->mTargetFPS && OswHal::getInstance()->isRequestFlush()) { + if ((!this->mEnableTargetFPS or millis() - lastFlush > 1000 / this->mTargetFPS) and OswHal::getInstance()->isRequestFlush()) { // Only draw overlays if enabled if (OswConfigAllKeys::settingDisplayOverlays.get()) // Only draw on first face if enabled, or on all others - if ((mainAppIndex == 0 && OswConfigAllKeys::settingDisplayOverlaysOnWatchScreen.get()) || mainAppIndex != 0) + if ((mainAppIndex == 0 and OswConfigAllKeys::settingDisplayOverlaysOnWatchScreen.get()) || mainAppIndex != 0) drawOverlays(); OswHal::getInstance()->flushCanvas(); lastFlush = millis(); diff --git a/src/overlays/overlays.cpp b/src/overlays/overlays.cpp index 999ffbd25..68c48a1d5 100644 --- a/src/overlays/overlays.cpp +++ b/src/overlays/overlays.cpp @@ -44,6 +44,7 @@ void drawWiFi(uint16_t x, uint16_t y) { } #endif +#ifndef OSW_EMULATOR void drawLowMemory(uint16_t x, uint16_t y) { if (!OswServiceAllTasks::memory.hasLowMemoryCondition()) return; @@ -56,6 +57,7 @@ void drawLowMemory(uint16_t x, uint16_t y) { gfx->drawLine(x + 7, y, x + 7, y + 14, OswUI::getInstance()->getDangerColor()); gfx->drawLine(x + 11, y, x + 11, y + 14, OswUI::getInstance()->getDangerColor()); } +#endif void drawOverlays() { bool drawBat = true; @@ -63,8 +65,9 @@ void drawOverlays() { // IF we have wifi enabled, we have to consider an additional condition to check drawBat = !OswServiceAllTasks::wifi.isEnabled(); #endif - +#ifndef OSW_EMULATOR drawLowMemory(84, 4); +#endif if (OswHal::getInstance()->isCharging()) drawUsbConnected(120 - 16, 6); // width is 31 diff --git a/src/services/OswServiceManager.cpp b/src/services/OswServiceManager.cpp index 4698362e8..16a5c241d 100644 --- a/src/services/OswServiceManager.cpp +++ b/src/services/OswServiceManager.cpp @@ -7,11 +7,17 @@ * This enables all currently known services using their setup() and starts the loop() on core 0 */ void OswServiceManager::setup() { - for (unsigned char i = 0; i < oswServiceTasksCount; i++) oswServiceTasks[i]->setup(); + for (unsigned char i = 0; i < oswServiceTasksCount; i++) + if(oswServiceTasks[i]) + oswServiceTasks[i]->setup(); this->active = true; +#ifndef OSW_EMULATOR xTaskCreatePinnedToCore([](void* pvParameters) -> void { OswServiceManager::getInstance().worker(); }, "oswServiceManager", this->workerStackSize /*stack*/, NULL /*input*/, 0 /*prio*/, &this->core0worker /*handle*/, 0); +#else + this->core0worker = new std::thread([]() -> void { OswServiceManager::getInstance().worker(); }); +#endif } /** @@ -30,15 +36,28 @@ void OswServiceManager::worker() { #ifndef NDEBUG Serial.println(String(__FILE__) + ": Background worker terminated!"); #endif +#ifndef OSW_EMULATOR vTaskDelete(nullptr); // Inform FreeRTOS this task is done - otherwise the kernel will take that personally and crash! +#endif } void OswServiceManager::loop() { for (unsigned char i = 0; i < oswServiceTasksCount; i++) - if (oswServiceTasks[i]->isRunning()) oswServiceTasks[i]->loop(); + if (oswServiceTasks[i]) + if (oswServiceTasks[i]->isRunning()) + oswServiceTasks[i]->loop(); } void OswServiceManager::stop() { - for (unsigned char i = 0; i < oswServiceTasksCount; i++) oswServiceTasks[i]->stop(); + for (unsigned char i = 0; i < oswServiceTasksCount; i++) + if(oswServiceTasks[i]) + oswServiceTasks[i]->stop(); this->active = false; +#ifdef OSW_EMULATOR + if(this->core0worker and this->core0worker->joinable()) { + this->core0worker->join(); + delete this->core0worker; + this->core0worker = nullptr; + } +#endif } \ No newline at end of file diff --git a/src/services/OswServiceTaskBLECompanion.cpp b/src/services/OswServiceTaskBLECompanion.cpp index a82a8147f..598cbe7cf 100644 --- a/src/services/OswServiceTaskBLECompanion.cpp +++ b/src/services/OswServiceTaskBLECompanion.cpp @@ -1,3 +1,4 @@ +#ifndef OSW_EMULATOR #include "./services/OswServiceTaskBLECompanion.h" #include "osw_hal.h" @@ -93,3 +94,4 @@ void OswServiceTaskBLECompanion::loop() { void OswServiceTaskBLECompanion::stop() { OswServiceTask::stop(); } +#endif \ No newline at end of file diff --git a/src/services/OswServiceTaskMemMonitor.cpp b/src/services/OswServiceTaskMemMonitor.cpp index 1db6d74b4..37f4d3a36 100644 --- a/src/services/OswServiceTaskMemMonitor.cpp +++ b/src/services/OswServiceTaskMemMonitor.cpp @@ -1,3 +1,4 @@ +#ifndef OSW_EMULATOR #include "./services/OswServiceTaskMemMonitor.h" #include "osw_hal.h" @@ -124,3 +125,4 @@ void OswServiceTaskMemMonitor::printStats() { // TODO Maybe fetch current largest available heap size and calc "fragmentation" percentage. #endif } +#endif \ No newline at end of file diff --git a/src/services/OswServiceTaskWiFi.cpp b/src/services/OswServiceTaskWiFi.cpp index d485be0ef..a025d1bc9 100644 --- a/src/services/OswServiceTaskWiFi.cpp +++ b/src/services/OswServiceTaskWiFi.cpp @@ -74,10 +74,14 @@ void OswServiceTaskWiFi::loop() { if(this->m_connectFailureCount % 4 == 3) { if(OswConfigAllKeys::wifiAutoAP.get()) { if(!this->m_enableStation) { - this->enableStation(); + if (OswConfigAllKeys::hostPasswordEnabled.get()) { + this->enableStation(OswConfigAllKeys::hostPass.get().c_str()); + } else { + this->enableStation(); + } this->m_enabledStationByAutoAP = time(nullptr); #ifndef NDEBUG - Serial.println(String(__FILE__) + ": [AutoAP] Active for " + String(this->m_enabledStationByAutoAPTimeout) + " seconds (password is " + this->m_stationPass + ")."); + Serial.println(String(__FILE__) + ": [AutoAP] Active for " + String(this->m_enabledStationByAutoAPTimeout) + " seconds (password is " + (this->m_stationPass.isEmpty() ? "-" : this->m_stationPass) + ")."); #endif } } else { @@ -265,19 +269,26 @@ bool OswServiceTaskWiFi::isStationEnabled() { /** * This enables the wifi station mode * - * @param password Set the wifi password to this (at least 8 chars!), otherwise a random password will be choosen. + * @param password Set the wifi password to this (at least 8 chars!), otherwise a random password will be choosen. This parameter can be ignored if the station password is inactive in the config. */ void OswServiceTaskWiFi::enableStation(const String& password) { + const bool usePassword = OswConfigAllKeys::hostPasswordEnabled.get(); this->m_hostname = OswConfigAllKeys::hostname.get(); - if(password.isEmpty()) - //Generate password - this->m_stationPass = String(random(10000000, 99999999)); //Generate random 8 chars long numeric password - else - this->m_stationPass = password; + if(usePassword) { + if(password.isEmpty() or password.length() < 8) + //Generate password + this->m_stationPass = String(random(10000000, 99999999)); //Generate random 8 chars long numeric password + else + this->m_stationPass = password; + } else + this->m_stationPass.clear(); // Clear the stored password, as we have it disabled anyways... this->m_enableStation = true; this->m_enabledStationByAutoAP = 0; //Revoke AutoAP station control this->updateWiFiConfig(); //This enables ap support - WiFi.softAP(this->m_hostname.c_str(), this->m_stationPass.c_str()); + if(usePassword) + WiFi.softAP(this->m_hostname.c_str(), this->m_stationPass.c_str()); + else + WiFi.softAP(this->m_hostname.c_str()); #ifndef NDEBUG Serial.println(String(__FILE__) + ": [Station] Enabled own station with SSID " + this->getStationSSID() + "..."); #endif @@ -292,6 +303,15 @@ void OswServiceTaskWiFi::disableStation() { #endif } +void OswServiceTaskWiFi::toggleAPPassword() { + OswConfig::getInstance()->enableWrite(); + OswConfigAllKeys::hostPasswordEnabled.set(!OswConfigAllKeys::hostPasswordEnabled.get()); +#ifndef NDEBUG + Serial.println(String(__FILE__) + ": [AP password]"+ String(" enabled : ")+ String(OswConfigAllKeys::hostPasswordEnabled.get()).c_str()); +#endif + OswConfig::getInstance()->disableWrite(); +} + IPAddress OswServiceTaskWiFi::getStationIP() { return this->m_enableStation ? WiFi.softAPIP() : IPAddress(); } diff --git a/src/services/OswServiceTasks.cpp b/src/services/OswServiceTasks.cpp index 4b588864a..be9df52d6 100644 --- a/src/services/OswServiceTasks.cpp +++ b/src/services/OswServiceTasks.cpp @@ -23,7 +23,9 @@ OswServiceTaskGPS gps; OswServiceTaskWiFi wifi; OswServiceTaskWebserver webserver; #endif +#ifndef OSW_EMULATOR OswServiceTaskMemMonitor memory; +#endif } // namespace OswServiceAllTasks OswServiceTask* oswServiceTasks[] = { @@ -38,6 +40,12 @@ OswServiceTask* oswServiceTasks[] = { #ifdef OSW_FEATURE_WIFI & OswServiceAllTasks::wifi, &OswServiceAllTasks::webserver, #endif +#ifndef OSW_EMULATOR +#ifndef NDEBUG & OswServiceAllTasks::memory +#endif +#else + nullptr // To prevent static array with size zero +#endif }; const unsigned char oswServiceTasksCount = OswUtil::size(oswServiceTasks);