diff --git a/.github/workflows/lv_sim.yml b/.github/workflows/lv_sim.yml
new file mode 100644
index 0000000..be2a3d3
--- /dev/null
+++ b/.github/workflows/lv_sim.yml
@@ -0,0 +1,66 @@
+# GitHub Actions Workflow to build Simulator for PineTime Smart Watch LVGL Interface
+
+# Name of this Workflow
+name: Build InfiniSim LVGL Simulator
+
+# When to run this Workflow...
+on:
+
+ # Run on all branches
+ push:
+ branches: []
+
+ # Also run this Workflow when a Pull Request is created or updated in the "main" and "develop" Branch
+ pull_request:
+ branches: [ main, develop ]
+
+# Steps to run for the Workflow
+jobs:
+ build:
+
+ # Run these steps on Ubuntu
+ runs-on: ubuntu-latest
+
+ steps:
+
+ #########################################################################################
+ # Download and Install Dependencies
+
+ - name: Install cmake
+ uses: lukka/get-cmake@v3.18.3
+
+ - name: Install SDL2 development package
+ run: |
+ sudo apt-get update
+ sudo apt-get -y install libsdl2-dev
+
+ #########################################################################################
+ # Checkout
+
+ - name: Checkout source files
+ uses: actions/checkout@v2
+ with:
+ submodules: recursive
+
+ #########################################################################################
+ # CMake
+
+ - name: CMake
+ run: |
+ cmake -G Ninja -S . -B build_lv_sim
+
+ #########################################################################################
+ # Build and Upload simulator
+
+ # For Debugging Builds: Remove "make" option "-j" for clearer output. Add "--trace" to see details.
+ # For Faster Builds: Add "make" option "-j"
+
+ - name: Build simulator executable
+ run: |
+ cmake --build build_lv_sim
+
+ - name: Upload simulator executable
+ uses: actions/upload-artifact@v2
+ with:
+ name: infinisim
+ path: build_lv_sim/infinisim
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..bb548ac
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,6 @@
+[submodule "InfiniTime"]
+ path = InfiniTime
+ url = ../../NeroBurner/InfiniTime.git
+[submodule "lv_drivers"]
+ path = lv_drivers
+ url = ../../lvgl/lv_drivers.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..fdfc94b
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,208 @@
+message(STATUS "Using CMake version ${CMAKE_VERSION}")
+cmake_minimum_required(VERSION 3.10...${CMAKE_VERSION})
+
+SET(InfiniTime_DIR "InfiniTime" CACHE PATH "Path to InfiniTime source path to use for simulator")
+
+if(NOT EXISTS ${InfiniTime_DIR})
+ message(FATAL_ERROR "Can't access folder '${InfiniTime_DIR}'. Try `git submodule update --init --recursive` to initialize the git submodule of InfiniTime")
+endif()
+if(NOT EXISTS ${InfiniTime_DIR}/src/libs/lvgl)
+ message(FATAL_ERROR "Can't access folder '${InfiniTime_DIR}/src/libs/lvgl'. Try `git submodule update --init --recursive` to initialize the git submodule of lvgl inside InfiniTime")
+endif()
+
+# try to extract the PineTime project version
+file(READ "${InfiniTime_DIR}/CMakeLists.txt" main_cmake)
+string(REGEX MATCH "project\\(pinetime VERSION ([0-9]*\.[0-9]*\.[0-9]*)" _ ${main_cmake})
+set(PROJECT_VERSION ${CMAKE_MATCH_1})
+message(STATUS "InfiniTime PROJECT_VERSION extracted: ${PROJECT_VERSION}")
+
+project(InfiniSim VERSION ${PROJECT_VERSION} LANGUAGES C CXX)
+# https://cmake.org/cmake/help/latest/prop_tgt/CXX_STANDARD.html
+string(COMPARE EQUAL "${CMAKE_CXX_STANDARD}" "" no_cmake_cxx_standard_set)
+if(no_cmake_cxx_standard_set)
+ set(CMAKE_CXX_STANDARD 17)
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
+ set(CMAKE_CXX_EXTENSIONS OFF)
+ message(STATUS "Using default C++ standard ${CMAKE_CXX_STANDARD}")
+else()
+ message(STATUS "Using user specified C++ standard ${CMAKE_CXX_STANDARD}")
+endif()
+set(CMAKE_C_STANDARD 11)#C11
+
+file(GLOB_RECURSE INCLUDES "lv_drivers/*.h" "${InfiniTime_DIR}/src/libs/lvgl/src/*.h" "./*.h" )
+file(GLOB_RECURSE SOURCES "lv_drivers/*.c" "${InfiniTime_DIR}/src/libs/lvgl/src/*.c" )
+
+add_executable(infinisim main.cpp ${SOURCES} ${INCLUDES})
+
+# add simulator files
+target_sources(infinisim PUBLIC
+ sim/displayapp/LittleVgl.h
+ sim/displayapp/LittleVgl.cpp
+ sim/displayapp/screens/Missing.h
+ sim/displayapp/screens/Missing.cpp
+ sim/components/battery/BatteryController.h
+ sim/components/battery/BatteryController.cpp
+ sim/components/ble/AlertNotificationService.h
+ sim/components/ble/AlertNotificationService.cpp
+ sim/components/ble/MusicService.h
+ sim/components/ble/MusicService.cpp
+ sim/components/ble/NavigationService.h
+ sim/components/ble/NavigationService.cpp
+ sim/components/ble/NimbleController.h
+ sim/components/ble/NimbleController.cpp
+ sim/components/ble/weather/WeatherService.h
+ sim/components/ble/weather/WeatherService.cpp
+ sim/components/brightness/BrightnessController.h
+ sim/components/brightness/BrightnessController.cpp
+ sim/components/firmwarevalidator/FirmwareValidator.h
+ sim/components/firmwarevalidator/FirmwareValidator.cpp
+ sim/components/fs/FS.h
+ sim/components/fs/FS.cpp
+ sim/components/heartrate/HeartRateController.h
+ sim/components/heartrate/HeartRateController.cpp
+ sim/components/motion/MotionController.h
+ sim/components/motion/MotionController.cpp
+ sim/components/motor/MotorController.h
+ sim/components/motor/MotorController.cpp
+ sim/drivers/Watchdog.h
+ sim/drivers/Watchdog.cpp
+ sim/drivers/Bma421.h
+ sim/drivers/Bma421.cpp
+ sim/drivers/Cst816s.h
+ sim/drivers/Cst816s.cpp
+ sim/drivers/Hrs3300.h
+ sim/drivers/Hrs3300.cpp
+ sim/drivers/SpiMaster.h
+ sim/drivers/SpiMaster.cpp
+ sim/drivers/TwiMaster.h
+ sim/drivers/TwiMaster.cpp
+ sim/drivers/SpiNorFlash.h
+ sim/drivers/SpiNorFlash.cpp
+ sim/heartratetask/HeartRateTask.h
+ sim/heartratetask/HeartRateTask.cpp
+ # FreeRTOS
+ sim/FreeRTOS.h
+ sim/FreeRTOS.cpp
+ sim/task.h
+ sim/task.cpp
+ sim/timers.h
+ sim/timers.cpp
+ sim/queue.h
+ sim/queue.cpp
+ # src/FreeRTOS
+ sim/portmacro_cmsis.h
+ sim/portmacro_cmsis.cpp
+ # nrf
+ sim/libraries/log/nrf_log.h
+ sim/libraries/delay/nrf_delay.h
+ sim/libraries/delay/nrf_delay.cpp
+ sim/nrfx/nrfx_log.h
+ sim/nrfx/drivers/include/nrfx_twi.h
+ sim/nrfx/hal/nrf_gpio.h
+ sim/nrfx/hal/nrf_gpio.cpp
+ sim/nrfx/hal/nrfx_gpiote.h # includes hal/nrf_gpio.h
+ sim/nrfx/hal/nrf_rtc.h
+ sim/nrfx/hal/nrf_rtc.cpp
+ # nrf/components/libraries/timer
+ sim/libraries/timer/app_timer.h
+ sim/libraries/timer/app_timer.cpp
+ sim/libraries/gpiote/app_gpiote.h # includes hal/nrf_gpio.h
+ )
+target_include_directories(infinisim PRIVATE "sim")
+target_include_directories(infinisim PRIVATE "sim/libraries/log") # for nrf_log.h
+target_include_directories(infinisim PRIVATE "sim/libraries/timer") # for app_timer.h
+target_include_directories(infinisim PRIVATE "sim/nrfx") # for nrfx_log.h and others
+target_include_directories(infinisim PRIVATE "sim/nrfx/hal") # for nrfx_log.h
+
+target_compile_definitions(infinisim PRIVATE LV_CONF_INCLUDE_SIMPLE)
+target_sources(infinisim PUBLIC
+ # LVGL Fonts
+ ${InfiniTime_DIR}/src/libs/lvgl/src/lv_font/lv_font_montserrat_14.c
+ ${InfiniTime_DIR}/src/libs/lvgl/src/lv_font/lv_font_montserrat_18.c
+ ${InfiniTime_DIR}/src/libs/lvgl/src/lv_font/lv_font_montserrat_22.c
+ ${InfiniTime_DIR}/src/libs/lvgl/src/lv_font/lv_font_montserrat_28.c
+
+ ${InfiniTime_DIR}/src/displayapp/fonts/lv_font_navi_80.c
+
+ ${InfiniTime_DIR}/src/displayapp/fonts/jetbrains_mono_extrabold_compressed.c
+ ${InfiniTime_DIR}/src/displayapp/fonts/jetbrains_mono_bold_20.c
+ ${InfiniTime_DIR}/src/displayapp/fonts/jetbrains_mono_76.c
+ ${InfiniTime_DIR}/src/displayapp/fonts/jetbrains_mono_42.c
+ ${InfiniTime_DIR}/src/displayapp/fonts/lv_font_sys_48.c
+ ${InfiniTime_DIR}/src/displayapp/fonts/open_sans_light.c
+)
+
+target_include_directories(infinisim PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}")
+target_include_directories(infinisim PRIVATE "${InfiniTime_DIR}/src/libs")
+target_include_directories(infinisim PRIVATE "lv_drivers")
+
+# add dates library
+target_include_directories(infinisim SYSTEM PRIVATE "${InfiniTime_DIR}/src/libs/date/includes")
+
+# add Screens with a globbing expression, to enable easier CI test-runs for PRs adding new Screens
+file(GLOB InfiniTime_SCREENS
+ "${InfiniTime_DIR}/src/displayapp/screens/*.h"
+ "${InfiniTime_DIR}/src/displayapp/screens/*.cpp"
+ "${InfiniTime_DIR}/src/displayapp/screens/settings/*.h"
+ "${InfiniTime_DIR}/src/displayapp/screens/settings/*.cpp"
+)
+target_sources(infinisim PUBLIC ${InfiniTime_SCREENS})
+
+# add files directly from InfiniTime sources
+target_include_directories(infinisim PRIVATE "${InfiniTime_DIR}/src")
+target_sources(infinisim PUBLIC
+ ${InfiniTime_DIR}/src/BootloaderVersion.h
+ ${InfiniTime_DIR}/src/BootloaderVersion.cpp
+ ${InfiniTime_DIR}/src/displayapp/Colors.h
+ ${InfiniTime_DIR}/src/displayapp/Colors.cpp
+ ${InfiniTime_DIR}/src/displayapp/DisplayApp.h
+ ${InfiniTime_DIR}/src/displayapp/DisplayApp.cpp
+ ${InfiniTime_DIR}/src/displayapp/lv_pinetime_theme.h
+ ${InfiniTime_DIR}/src/displayapp/lv_pinetime_theme.c
+ ${InfiniTime_DIR}/src/displayapp/icons/bg_clock.c # used by WatchFaceAnalog.cpp
+ ${InfiniTime_DIR}/src/buttonhandler/ButtonHandler.h
+ ${InfiniTime_DIR}/src/buttonhandler/ButtonHandler.cpp
+ ${InfiniTime_DIR}/src/components/alarm/AlarmController.h
+ ${InfiniTime_DIR}/src/components/alarm/AlarmController.cpp
+ ${InfiniTime_DIR}/src/components/ble/BleController.h
+ ${InfiniTime_DIR}/src/components/ble/BleController.cpp
+ ${InfiniTime_DIR}/src/components/datetime/DateTimeController.h
+ ${InfiniTime_DIR}/src/components/datetime/DateTimeController.cpp
+ ${InfiniTime_DIR}/src/components/settings/Settings.h
+ ${InfiniTime_DIR}/src/components/settings/Settings.cpp
+ ${InfiniTime_DIR}/src/components/ble/NotificationManager.h
+ ${InfiniTime_DIR}/src/components/ble/NotificationManager.cpp
+ ${InfiniTime_DIR}/src/components/timer/TimerController.h
+ ${InfiniTime_DIR}/src/components/timer/TimerController.cpp
+ ${InfiniTime_DIR}/src/drivers/PinMap.h
+ ${InfiniTime_DIR}/src/drivers/Spi.h
+ ${InfiniTime_DIR}/src/drivers/Spi.cpp
+ ${InfiniTime_DIR}/src/drivers/St7789.h
+ ${InfiniTime_DIR}/src/drivers/St7789.cpp
+ ${InfiniTime_DIR}/src/touchhandler/TouchHandler.h
+ ${InfiniTime_DIR}/src/touchhandler/TouchHandler.cpp
+ ${InfiniTime_DIR}/src/systemtask/SystemTask.h
+ ${InfiniTime_DIR}/src/systemtask/SystemTask.cpp
+)
+
+
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+# Special case for SDL2 dependency, goal is to find a config that exports SDL2::SDL2 target
+# libsdl2-dev has a `sdl2-config.cmake` that doesn't export this, but vcpkg does..
+find_package(SDL2 CONFIG QUIET)
+if(NOT TARGET SDL2::SDL2)
+ find_package(SDL2 MODULE REQUIRED)
+endif()
+target_link_libraries(infinisim PRIVATE SDL2::SDL2)
+
+# Get the latest abbreviated commit hash of the working branch
+execute_process(
+ COMMAND ${GIT_EXECUTABLE} log -1 --format=%h
+ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+ OUTPUT_VARIABLE PROJECT_GIT_COMMIT_HASH
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+)
+set(VERSION_EDIT_WARNING "// Do not edit this file, it is automatically generated by CMAKE!")
+configure_file("${InfiniTime_DIR}/src/Version.h.in" "${CMAKE_CURRENT_BINARY_DIR}/Version.h")
+
+target_include_directories(infinisim PRIVATE "${CMAKE_CURRENT_BINARY_DIR}")
diff --git a/InfiniTime b/InfiniTime
new file mode 160000
index 0000000..7142de2
--- /dev/null
+++ b/InfiniTime
@@ -0,0 +1 @@
+Subproject commit 7142de2aa65bddbde7fda341bde2124952beda2c
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e72bfdd
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ Copyright (C)
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..74f0929
--- /dev/null
+++ b/README.md
@@ -0,0 +1,106 @@
+# [InfiniSim](https://github.com/InfiniTimeOrg/InfiniSim)
+
+[![Build InfiniSim LVGL Simulator](https://github.com/InfiniTimeOrg/InfiniSim/actions/workflows/lv_sim.yml/badge.svg)](https://github.com/InfiniTimeOrg/InfiniSim/actions/workflows/lv_sim.yml)
+
+Simulator for [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime) project.
+
+Experience the `InfiniTime` user interface directly on your PC, to shorten the time until you get your hands on a real [PineTime smartwatch](https://www.pine64.org/pinetime/).
+Or use it to develop new Watchfaces, new Screens, or quickly iterate on the user interface.
+
+For a history on how this simulator started and the challenges on its way visit the [original PR](https://github.com/InfiniTimeOrg/InfiniTime/pull/743).
+
+## Build dependencies
+
+- CMake
+- SDL2 (provides the simulator window, handles mouse and keyboard input)
+- Compiler (g++ or clang++)
+
+On Ubuntu/Debian install the following packages:
+
+```sh
+sudo apt get install -y cmake libsdl2-dev g++
+```
+
+On Arch Linux the following packages are needed:
+
+```sh
+sudo pacman -S cmake sdl2 gcc
+```
+
+## Get the Sources
+
+Clone this repository and tell `git` to recursively download the submodules as well
+
+```sh
+git clone --recursive https://github.com/InfiniTimeOrg/InfiniSim.git
+```
+
+If you've already cloned the repository without the submodules (or you want to update them to the latest checked in version) run the following command:
+
+```sh
+git submodule update --init --recursive
+```
+
+## Configure and Build
+
+In the most basic configuration tell cmake to configure the project and build it with the following two commands:
+
+```sh
+cmake -S . -B build
+cmake --build build -j4
+```
+
+The following configuration settings can be added to the first `cmake -S . -B build` call
+
+- `-DInfiniTime_DIR=InfiniTime`: a full path to an existing InfiniTime repository checked out.
+ Inside that directory the `src/libs/lvgl` submodule must be checked out as well.
+ The default value points to the InfiniTime submodule in this repository.
+
+## Run Simulator
+
+When the build was successful the simulator binary can be started with
+
+```sh
+./build/infinisim
+```
+
+![Running Simulator](https://user-images.githubusercontent.com/9076163/151057090-66fa6b10-eb4f-4b62-88e6-f9f307a57e40.gif)
+
+To hide the second simulator-status-window start the binary with the `--hide-status` option
+
+```sh
+./build/infinisim --hide-status
+```
+
+- Left mouse button: simulates your finger, just click to tap, click and drag to swipe
+- Right mouse button: simulates the hardware button (for example turn the screen off or on again)
+
+Using the keyboard the following events can be triggered:
+
+- `r` ... enable ringing
+- `R` ... disable ringing
+- `m` ... let motor run for 100 ms
+- `M` ... let motor run for 255 ms
+- `n` ... send notification
+- `N` ... clear all notifications
+- `b` ... connect Bluetooth
+- `B` ... disconnect Bluetooth
+- `v` ... increase battery voltage and percentage
+- `V` ... decrease battery voltage and percentage
+- `c` ... charging,
+- `C` ... not charging
+- `l` ... increase brightness level
+- `L` ... lower brightness level
+- `p` ... enable print lvgl memory usage to terminal
+- `P` ... disable print memory usage
+- `s` ... increase step count by 500 steps
+- `S` ... decrease step count by 500 steps
+- `h` ... set heartrate running, and on further presses increase by 10 bpm
+- `H` ... stop heartrate
+
+## Licenses
+
+This project is released under the GNU General Public License version 3 or, at your option, any later version.
+The same license as [InfiniTime](https://github.com/InfiniTimeOrg/InfiniTime).
+
+The simulator is based on [lv_sim_eclipse_sdl](https://github.com/lvgl/lv_sim_eclipse_sdl) project under the MIT license.
diff --git a/cmake/FindSDL2.cmake b/cmake/FindSDL2.cmake
new file mode 100644
index 0000000..e39634c
--- /dev/null
+++ b/cmake/FindSDL2.cmake
@@ -0,0 +1,144 @@
+# Module: FindSDL2.cmake
+# Based on: https://github.com/mosra/magnum/blob/master/modules/FindSDL2.cmake
+#
+# Exports:
+# SDL2::SDL2 - Imported target
+# SDL2_FOUND - True if SDL2 was found.
+#
+# Additionally these variables are defined for internal usage:
+#
+# SDL2_LIBRARY_DEBUG - SDL2 debug library, if found
+# SDL2_LIBRARY_RELEASE - SDL2 release library, if found
+# SDL2_DLL_DEBUG - SDL2 debug DLL on Windows, if found
+# SDL2_DLL_RELEASE - SDL2 release DLL on Windows, if found
+# SDL2_INCLUDE_DIR - Root include dir
+
+set(_SDL2_PATH_SUFFIXES SDL2)
+if(WIN32)
+ # Precompiled libraries for MSVC are in x86/x64 subdirectories
+ if(MSVC)
+ if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+ set(_SDL2_LIBRARY_PATH_SUFFIX lib/x64)
+ elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
+ set(_SDL2_LIBRARY_PATH_SUFFIX lib/x86)
+ endif()
+
+ # Both includes and libraries for MinGW are in some directory deep
+ # inside. There's also a CMake config file but it has HARDCODED path
+ # to /opt/local/i686-w64-mingw32, which doesn't make ANY SENSE,
+ # especially on Windows.
+ elseif(MINGW)
+ if(CMAKE_SIZEOF_VOID_P EQUAL 8)
+ set(_SDL2_LIBRARY_PATH_SUFFIX x86_64-w64-mingw32/lib)
+ set(_SDL2_RUNTIME_PATH_SUFFIX x86_64-w64-mingw32/bin)
+ list(APPEND _SDL2_PATH_SUFFIXES x86_64-w64-mingw32/include/SDL2)
+ elseif(CMAKE_SIZEOF_VOID_P EQUAL 4)
+ set(_SDL2_LIBRARY_PATH_SUFFIX i686-w64-mingw32/lib)
+ set(_SDL2_RUNTIME_PATH_SUFFIX i686-w64-mingw32/lib)
+ list(APPEND _SDL2_PATH_SUFFIXES i686-w64-mingw32/include/SDL2)
+ endif()
+ else()
+ message(FATAL_ERROR "Unsupported compiler")
+ endif()
+endif()
+
+find_library(SDL2_LIBRARY_RELEASE
+ NAMES SDL2-2.0 SDL2
+ PATH_SUFFIXES ${_SDL2_LIBRARY_PATH_SUFFIX})
+find_library(SDL2_LIBRARY_DEBUG
+ NAMES SDL2d
+ PATH_SUFFIXES ${_SDL2_LIBRARY_PATH_SUFFIX})
+
+set(SDL2_LIBRARY_NEEDED SDL2_LIBRARY)
+
+include(SelectLibraryConfigurations)
+select_library_configurations(SDL2)
+
+# Include dir
+find_path(SDL2_INCLUDE_DIR
+ # We must search file which is present only in SDL2 and not in SDL1.
+ # Apparently when both SDL.h and SDL_scancode.h are specified, CMake is
+ # happy enough that it found SDL.h and doesn't bother about the other.
+ #
+ # On macOS, where the includes are not in SDL2/SDL.h form (which would
+ # solve this issue), but rather SDL2.framework/Headers/SDL.h, CMake might
+ # find SDL.framework/Headers/SDL.h if SDL1 is installed, which is wrong.
+ NAMES SDL_scancode.h
+ PATH_SUFFIXES ${_SDL2_PATH_SUFFIXES})
+
+if(WIN32)
+ find_file(SDL2_DLL_RELEASE
+ NAMES SDL2.dll
+ PATH_SUFFIXES ${_SDL2_RUNTIME_PATH_SUFFIX} ${_SDL2_LIBRARY_PATH_SUFFIX})
+ find_file(SDL2_DLL_DEBUG
+ NAMES SDL2d.dll
+ PATH_SUFFIXES ${_SDL2_RUNTIME_PATH_SUFFIX} ${_SDL2_LIBRARY_PATH_SUFFIX})
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args("SDL2" DEFAULT_MSG
+ ${SDL2_LIBRARY_NEEDED}
+ ${_SDL2_FRAMEWORK_LIBRARY_NAMES}
+ SDL2_INCLUDE_DIR)
+
+if(NOT TARGET SDL2::SDL2)
+ if(SDL2_LIBRARY_NEEDED)
+ add_library(SDL2::SDL2 UNKNOWN IMPORTED)
+
+ # Work around BUGGY framework support on macOS
+ # https://cmake.org/Bug/view.php?id=14105
+ if(APPLE AND SDL2_LIBRARY_RELEASE MATCHES "\\.framework$")
+ set_property(TARGET SDL2::SDL2 APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
+ set_property(TARGET SDL2::SDL2 PROPERTY IMPORTED_LOCATION_RELEASE ${SDL2_LIBRARY_RELEASE}/SDL2)
+ else()
+ if(SDL2_LIBRARY_RELEASE)
+ set_property(TARGET SDL2::SDL2 APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
+ set_property(TARGET SDL2::SDL2 PROPERTY IMPORTED_LOCATION_RELEASE ${SDL2_LIBRARY_RELEASE})
+ endif()
+
+ if(SDL2_LIBRARY_DEBUG)
+ set_property(TARGET SDL2::SDL2 APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
+ set_property(TARGET SDL2::SDL2 PROPERTY IMPORTED_LOCATION_DEBUG ${SDL2_LIBRARY_DEBUG})
+ endif()
+ endif()
+
+ # Link additional `dl` and `pthread` libraries required by a static
+ # build of SDL on Unixy platforms (except Apple, where it is most
+ # probably some frameworks instead)
+ if(UNIX AND NOT APPLE AND (SDL2_LIBRARY_DEBUG MATCHES "${CMAKE_STATIC_LIBRARY_SUFFIX}$" OR SDL2_LIBRARY_RELEASE MATCHES "${CMAKE_STATIC_LIBRARY_SUFFIX}$"))
+ find_package(Threads REQUIRED)
+ set_property(TARGET SDL2::SDL2 APPEND PROPERTY
+ INTERFACE_LINK_LIBRARIES Threads::Threads ${CMAKE_DL_LIBS})
+ endif()
+
+ # Windows dependencies for a static library. Unfortunately there's no
+ # easy way to figure out if a *.lib is static or dynamic, so we're
+ # adding only if a DLL is not found.
+ if(WIN32 AND NOT SDL2_DLL_RELEASE AND NOT SDL2_DLL_DEBUG)
+ set_property(TARGET SDL2::SDL2 APPEND PROPERTY INTERFACE_LINK_LIBRARIES
+ # https://github.com/SDL-mirror/SDL/blob/release-2.0.10/CMakeLists.txt#L1338
+ user32 gdi32 winmm imm32 ole32 oleaut32 version uuid advapi32 setupapi shell32
+ # https://github.com/SDL-mirror/SDL/blob/release-2.0.10/CMakeLists.txt#L1384
+ dinput8)
+ # https://github.com/SDL-mirror/SDL/blob/release-2.0.10/CMakeLists.txt#L1422
+ # additionally has dxerr for MSVC if DirectX SDK is not used, but
+ # according to https://walbourn.github.io/wheres-dxerr-lib/ this
+ # thing is long deprecated.
+ if(MINGW)
+ set_property(TARGET SDL2::SDL2 APPEND PROPERTY INTERFACE_LINK_LIBRARIES
+ # https://github.com/SDL-mirror/SDL/blob/release-2.0.10/CMakeLists.txt#L1386
+ dxerr8
+ # https://github.com/SDL-mirror/SDL/blob/release-2.0.10/CMakeLists.txt#L1388
+ mingw32)
+ endif()
+ endif()
+
+ else()
+ add_library(SDL2::SDL2 INTERFACE IMPORTED)
+ endif()
+
+ set_property(TARGET SDL2::SDL2 PROPERTY
+ INTERFACE_INCLUDE_DIRECTORIES ${SDL2_INCLUDE_DIR})
+endif()
+
+mark_as_advanced(SDL2_INCLUDE_DIR)
\ No newline at end of file
diff --git a/lv_conf.h b/lv_conf.h
new file mode 100644
index 0000000..74598a1
--- /dev/null
+++ b/lv_conf.h
@@ -0,0 +1,766 @@
+/**
+ * @file lv_conf.h
+ * Configuration file for v7.7.0-dev
+ */
+
+#ifndef LV_CONF_H
+#define LV_CONF_H
+/* clang-format off */
+
+#include
+
+/*====================
+ Graphical settings
+ *====================*/
+
+/* Maximal horizontal and vertical resolution to support by the library.*/
+#define LV_HOR_RES_MAX (240)
+#define LV_VER_RES_MAX (240)
+
+/* Color depth:
+ * - 1: 1 byte per pixel
+ * - 8: RGB332
+ * - 16: RGB565
+ * - 32: ARGB8888
+ */
+#define LV_COLOR_DEPTH 16
+
+/* Swap the 2 bytes of RGB565 color.
+ * Useful if the display has a 8 bit interface (e.g. SPI)*/
+#define LV_COLOR_16_SWAP 1
+
+/* 1: Enable screen transparency.
+ * Useful for OSD or other overlapping GUIs.
+ * Requires `LV_COLOR_DEPTH = 32` colors and the screen's style should be modified: `style.body.opa = ...`*/
+#define LV_COLOR_SCREEN_TRANSP 0
+
+/*Images pixels with this color will not be drawn (with chroma keying)*/
+#define LV_COLOR_TRANSP LV_COLOR_MAKE(0x6c, 0xFc, 0x6a) /*LV_COLOR_LIME: pure green*/
+
+/* Enable anti-aliasing (lines, and radiuses will be smoothed) */
+#define LV_ANTIALIAS 1
+
+/* Default display refresh period.
+ * Can be changed in the display driver (`lv_disp_drv_t`).*/
+#define LV_DISP_DEF_REFR_PERIOD 20 /*[ms]*/
+
+/* Dot Per Inch: used to initialize default sizes.
+ * E.g. a button with width = LV_DPI / 2 -> half inch wide
+ * (Not so important, you can adjust it to modify default sizes and spaces)*/
+#define LV_DPI 100 /*[px]*/
+
+/* The the real width of the display changes some default values:
+ * default object sizes, layout of examples, etc.
+ * According to the width of the display (hor. res. / dpi)
+ * the displays fall in 4 categories.
+ * The 4th is extra large which has no upper limit so not listed here
+ * The upper limit of the categories are set below in 0.1 inch unit.
+ */
+#define LV_DISP_SMALL_LIMIT 30
+#define LV_DISP_MEDIUM_LIMIT 50
+#define LV_DISP_LARGE_LIMIT 70
+
+/* Type of coordinates. Should be `int16_t` (or `int32_t` for extreme cases) */
+typedef int16_t lv_coord_t;
+
+/*=========================
+ Memory manager settings
+ *=========================*/
+
+/* LittelvGL's internal memory manager's settings.
+ * The graphical objects and other related data are stored here. */
+
+/* 1: use custom malloc/free, 0: use the built-in `lv_mem_alloc` and `lv_mem_free` */
+#define LV_MEM_CUSTOM 0
+#if LV_MEM_CUSTOM == 0
+/* Size of the memory used by `lv_mem_alloc` in bytes (>= 2kB)*/
+#ifdef NDEBUG
+#define LV_MEM_SIZE (14U * 1024U)
+#else // debug mode -> allow more memory
+#define LV_MEM_SIZE (28U * 1024U)
+#endif
+
+/* Complier prefix for a big array declaration */
+#define LV_MEM_ATTR
+
+/* Set an address for the memory pool instead of allocating it as an array.
+ * Can be in external SRAM too. */
+#define LV_MEM_ADR 0
+
+/* Automatically defrag. on free. Defrag. means joining the adjacent free cells. */
+#define LV_MEM_AUTO_DEFRAG 1
+#else /*LV_MEM_CUSTOM*/
+#define LV_MEM_CUSTOM_INCLUDE /*Header for the dynamic memory function*/
+#define LV_MEM_CUSTOM_ALLOC malloc /*Wrapper to malloc*/
+#define LV_MEM_CUSTOM_FREE free /*Wrapper to free*/
+#endif /*LV_MEM_CUSTOM*/
+
+/* Use the standard memcpy and memset instead of LVGL's own functions.
+ * The standard functions might or might not be faster depending on their implementation. */
+#define LV_MEMCPY_MEMSET_STD 1
+
+/* Garbage Collector settings
+ * Used if lvgl is binded to higher level language and the memory is managed by that language */
+#define LV_ENABLE_GC 0
+#if LV_ENABLE_GC != 0
+#define LV_GC_INCLUDE "gc.h" /*Include Garbage Collector related things*/
+#define LV_MEM_CUSTOM_REALLOC your_realloc /*Wrapper to realloc*/
+#define LV_MEM_CUSTOM_GET_SIZE your_mem_get_size /*Wrapper to lv_mem_get_size*/
+#endif /* LV_ENABLE_GC */
+
+/*=======================
+ Input device settings
+ *=======================*/
+
+/* Input device default settings.
+ * Can be changed in the Input device driver (`lv_indev_drv_t`)*/
+
+/* Input device read period in milliseconds */
+#define LV_INDEV_DEF_READ_PERIOD 20
+
+/* Drag threshold in pixels */
+#define LV_INDEV_DEF_DRAG_LIMIT 10
+
+/* Drag throw slow-down in [%]. Greater value -> faster slow-down */
+#define LV_INDEV_DEF_DRAG_THROW 20
+
+/* Long press time in milliseconds.
+ * Time to send `LV_EVENT_LONG_PRESSSED`) */
+#define LV_INDEV_DEF_LONG_PRESS_TIME 400
+
+/* Repeated trigger period in long press [ms]
+ * Time between `LV_EVENT_LONG_PRESSED_REPEAT */
+#define LV_INDEV_DEF_LONG_PRESS_REP_TIME 100
+
+/* Gesture threshold in pixels */
+#define LV_INDEV_DEF_GESTURE_LIMIT 50
+
+/* Gesture min velocity at release before swipe (pixels)*/
+#define LV_INDEV_DEF_GESTURE_MIN_VELOCITY 3
+
+/*==================
+ * Feature usage
+ *==================*/
+
+/*1: Enable the Animations */
+#define LV_USE_ANIMATION 1
+#if LV_USE_ANIMATION
+
+/*Declare the type of the user data of animations (can be e.g. `void *`, `int`, `struct`)*/
+typedef void* lv_anim_user_data_t;
+
+#endif
+
+/* 1: Enable shadow drawing on rectangles*/
+#define LV_USE_SHADOW 0
+#if LV_USE_SHADOW
+/* Allow buffering some shadow calculation
+ * LV_SHADOW_CACHE_SIZE is the max. shadow size to buffer,
+ * where shadow size is `shadow_width + radius`
+ * Caching has LV_SHADOW_CACHE_SIZE^2 RAM cost*/
+#define LV_SHADOW_CACHE_SIZE 0
+#endif
+
+/*1: enable outline drawing on rectangles*/
+#define LV_USE_OUTLINE 0
+
+/*1: enable pattern drawing on rectangles*/
+#define LV_USE_PATTERN 0
+
+/*1: enable value string drawing on rectangles*/
+#define LV_USE_VALUE_STR 1
+
+/* 1: Use other blend modes than normal (`LV_BLEND_MODE_...`)*/
+#define LV_USE_BLEND_MODES 0
+
+/* 1: Use the `opa_scale` style property to set the opacity of an object and its children at once*/
+#define LV_USE_OPA_SCALE 0
+
+/* 1: Use image zoom and rotation*/
+#define LV_USE_IMG_TRANSFORM 0
+
+/* 1: Enable object groups (for keyboard/encoder navigation) */
+#define LV_USE_GROUP 0
+#if LV_USE_GROUP
+typedef void* lv_group_user_data_t;
+#endif /*LV_USE_GROUP*/
+
+/* 1: Enable GPU interface*/
+#define LV_USE_GPU 0 /*Only enables `gpu_fill_cb` and `gpu_blend_cb` in the disp. drv- */
+#define LV_USE_GPU_STM32_DMA2D 0
+/*If enabling LV_USE_GPU_STM32_DMA2D, LV_GPU_DMA2D_CMSIS_INCLUDE must be defined to include path of CMSIS header of target processor
+e.g. "stm32f769xx.h" or "stm32f429xx.h" */
+#define LV_GPU_DMA2D_CMSIS_INCLUDE
+
+/*1: Use PXP for CPU off-load on NXP RTxxx platforms */
+#define LV_USE_GPU_NXP_PXP 0
+
+/*1: Add default bare metal and FreeRTOS interrupt handling routines for PXP (lv_gpu_nxp_pxp_osa.c)
+ * and call lv_gpu_nxp_pxp_init() automatically during lv_init(). Note that symbol FSL_RTOS_FREE_RTOS
+ * has to be defined in order to use FreeRTOS OSA, otherwise bare-metal implementation is selected.
+ *0: lv_gpu_nxp_pxp_init() has to be called manually before lv_init()
+ * */
+#define LV_USE_GPU_NXP_PXP_AUTO_INIT 0
+
+/*1: Use VG-Lite for CPU offload on NXP RTxxx platforms */
+#define LV_USE_GPU_NXP_VG_LITE 0
+
+/* 1: Enable file system (might be required for images */
+// TODO: Enable FS
+#define LV_USE_FILESYSTEM 1
+#if LV_USE_FILESYSTEM
+/*Declare the type of the user data of file system drivers (can be e.g. `void *`, `int`, `struct`)*/
+typedef void * lv_fs_drv_user_data_t;
+#endif
+
+/*1: Add a `user_data` to drivers and objects*/
+#define LV_USE_USER_DATA 1
+
+/*1: Show CPU usage and FPS count in the right bottom corner*/
+#define LV_USE_PERF_MONITOR 0
+
+/*1: Use the functions and types from the older API if possible */
+#define LV_USE_API_EXTENSION_V6 0
+#define LV_USE_API_EXTENSION_V7 1
+
+/*========================
+ * Image decoder and cache
+ *========================*/
+
+/* 1: Enable indexed (palette) images */
+#define LV_IMG_CF_INDEXED 1
+
+/* 1: Enable alpha indexed images */
+#define LV_IMG_CF_ALPHA 0
+
+/* Default image cache size. Image caching keeps the images opened.
+ * If only the built-in image formats are used there is no real advantage of caching.
+ * (I.e. no new image decoder is added)
+ * With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images.
+ * However the opened images might consume additional RAM.
+ * LV_IMG_CACHE_DEF_SIZE must be >= 1 */
+#define LV_IMG_CACHE_DEF_SIZE 6
+
+/*Declare the type of the user data of image decoder (can be e.g. `void *`, `int`, `struct`)*/
+typedef void* lv_img_decoder_user_data_t;
+
+/*=====================
+ * Compiler settings
+ *====================*/
+
+/* For big endian systems set to 1 */
+#define LV_BIG_ENDIAN_SYSTEM 0
+
+/* Define a custom attribute to `lv_tick_inc` function */
+#define LV_ATTRIBUTE_TICK_INC
+
+/* Define a custom attribute to `lv_task_handler` function */
+#define LV_ATTRIBUTE_TASK_HANDLER
+
+/* Define a custom attribute to `lv_disp_flush_ready` function */
+#define LV_ATTRIBUTE_FLUSH_READY
+
+/* Required alignment size for buffers */
+#define LV_ATTRIBUTE_MEM_ALIGN_SIZE
+
+/* With size optimization (-Os) the compiler might not align data to
+ * 4 or 8 byte boundary. Some HW may need even 32 or 64 bytes.
+ * This alignment will be explicitly applied where needed.
+ * LV_ATTRIBUTE_MEM_ALIGN_SIZE should be used to specify required align size.
+ * E.g. __attribute__((aligned(LV_ATTRIBUTE_MEM_ALIGN_SIZE))) */
+#define LV_ATTRIBUTE_MEM_ALIGN
+
+/* Attribute to mark large constant arrays for example
+ * font's bitmaps */
+#define LV_ATTRIBUTE_LARGE_CONST
+
+/* Prefix performance critical functions to place them into a faster memory (e.g RAM)
+ * Uses 15-20 kB extra memory */
+//#define LV_ATTRIBUTE_FAST_MEM
+
+/* Export integer constant to binding.
+ * This macro is used with constants in the form of LV_ that
+ * should also appear on lvgl binding API such as Micropython
+ *
+ * The default value just prevents a GCC warning.
+ */
+#define LV_EXPORT_CONST_INT(int_value) struct _silence_gcc_warning
+
+/* Prefix variables that are used in GPU accelerated operations, often these need to be
+ * placed in RAM sections that are DMA accessible */
+// #define LV_ATTRIBUTE_DMA
+
+/*===================
+ * HAL settings
+ *==================*/
+
+/* 1: use a custom tick source.
+ * It removes the need to manually update the tick with `lv_tick_inc`) */
+#define LV_TICK_CUSTOM 0
+#if LV_TICK_CUSTOM == 1
+#define LV_TICK_CUSTOM_INCLUDE "FreeRTOS.h" /*Header for the system time function*/
+uint32_t xTaskGetTickCount(); /*Forward declare to avoid compiler warning*/
+#define LV_TICK_CUSTOM_SYS_TIME_EXPR (xTaskGetTickCount()) /*Expression evaluating to current system time in ms*/
+#endif /*LV_TICK_CUSTOM*/
+
+typedef void* lv_disp_drv_user_data_t; /*Type of user data in the display driver*/
+typedef void* lv_indev_drv_user_data_t; /*Type of user data in the input device driver*/
+
+/*================
+ * Log settings
+ *===============*/
+
+/*1: Enable the log module*/
+#define LV_USE_LOG 0
+#if LV_USE_LOG
+/* How important log should be added:
+ * LV_LOG_LEVEL_TRACE A lot of logs to give detailed information
+ * LV_LOG_LEVEL_INFO Log important events
+ * LV_LOG_LEVEL_WARN Log if something unwanted happened but didn't cause a problem
+ * LV_LOG_LEVEL_ERROR Only critical issue, when the system may fail
+ * LV_LOG_LEVEL_NONE Do not log anything
+ */
+#define LV_LOG_LEVEL LV_LOG_LEVEL_WARN
+
+/* 1: Print the log with 'printf';
+ * 0: user need to register a callback with `lv_log_register_print_cb`*/
+#define LV_LOG_PRINTF 0
+#endif /*LV_USE_LOG*/
+
+/*=================
+ * Debug settings
+ *================*/
+
+/* If Debug is enabled LittelvGL validates the parameters of the functions.
+ * If an invalid parameter is found an error log message is printed and
+ * the MCU halts at the error. (`LV_USE_LOG` should be enabled)
+ * If you are debugging the MCU you can pause
+ * the debugger to see exactly where the issue is.
+ *
+ * The behavior of asserts can be overwritten by redefining them here.
+ * E.g. #define LV_ASSERT_MEM(p)
+ */
+#define LV_USE_DEBUG 0
+#if LV_USE_DEBUG
+
+/*Check if the parameter is NULL. (Quite fast) */
+#define LV_USE_ASSERT_NULL 1
+
+/*Checks is the memory is successfully allocated or no. (Quite fast)*/
+#define LV_USE_ASSERT_MEM 1
+
+/*Check the integrity of `lv_mem` after critical operations. (Slow)*/
+#define LV_USE_ASSERT_MEM_INTEGRITY 0
+
+/* Check the strings.
+ * Search for NULL, very long strings, invalid characters, and unnatural repetitions. (Slow)
+ * If disabled `LV_USE_ASSERT_NULL` will be performed instead (if it's enabled) */
+#define LV_USE_ASSERT_STR 0
+
+/* Check NULL, the object's type and existence (e.g. not deleted). (Quite slow)
+ * If disabled `LV_USE_ASSERT_NULL` will be performed instead (if it's enabled) */
+#define LV_USE_ASSERT_OBJ 0
+
+/*Check if the styles are properly initialized. (Fast)*/
+#define LV_USE_ASSERT_STYLE 0
+
+#endif /*LV_USE_DEBUG*/
+
+/*==================
+ * FONT USAGE
+ *===================*/
+
+/* The built-in fonts contains the ASCII range and some Symbols with 4 bit-per-pixel.
+ * The symbols are available via `LV_SYMBOL_...` defines
+ * More info about fonts: https://docs.lvgl.io/v7/en/html/overview/font.html
+ * To create a new font go to: https://lvgl.com/ttf-font-to-c-array
+ */
+
+/* Montserrat fonts with bpp = 4
+ * https://fonts.google.com/specimen/Montserrat */
+#define LV_FONT_MONTSERRAT_8 0
+#define LV_FONT_MONTSERRAT_10 0
+#define LV_FONT_MONTSERRAT_12 0
+#define LV_FONT_MONTSERRAT_14 0
+#define LV_FONT_MONTSERRAT_16 0
+#define LV_FONT_MONTSERRAT_18 0
+#define LV_FONT_MONTSERRAT_20 0
+#define LV_FONT_MONTSERRAT_22 0
+#define LV_FONT_MONTSERRAT_24 0
+#define LV_FONT_MONTSERRAT_26 0
+#define LV_FONT_MONTSERRAT_28 0
+#define LV_FONT_MONTSERRAT_30 0
+#define LV_FONT_MONTSERRAT_32 0
+#define LV_FONT_MONTSERRAT_34 0
+#define LV_FONT_MONTSERRAT_36 0
+#define LV_FONT_MONTSERRAT_38 0
+#define LV_FONT_MONTSERRAT_40 0
+#define LV_FONT_MONTSERRAT_42 0
+#define LV_FONT_MONTSERRAT_44 0
+#define LV_FONT_MONTSERRAT_46 0
+#define LV_FONT_MONTSERRAT_48 0
+
+/* Demonstrate special features */
+#define LV_FONT_MONTSERRAT_12_SUBPX 0
+#define LV_FONT_MONTSERRAT_28_COMPRESSED 0 /*bpp = 3*/
+#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 0 /*Hebrew, Arabic, PErisan letters and all their forms*/
+#define LV_FONT_SIMSUN_16_CJK 0 /*1000 most common CJK radicals*/
+
+/*Pixel perfect monospace font
+ * http://pelulamu.net/unscii/ */
+#define LV_FONT_UNSCII_8 0
+
+/* Optionally declare your custom fonts here.
+ * You can use these fonts as default font too
+ * and they will be available globally. E.g.
+ * #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_1) \
+ * LV_FONT_DECLARE(my_font_2)
+ */
+
+#define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(jetbrains_mono_bold_20) \
+ LV_FONT_DECLARE(jetbrains_mono_extrabold_compressed) \
+ LV_FONT_DECLARE(jetbrains_mono_42) \
+ LV_FONT_DECLARE(jetbrains_mono_76) \
+ LV_FONT_DECLARE(open_sans_light) \
+ LV_FONT_DECLARE(lv_font_sys_48)
+
+/* Enable it if you have fonts with a lot of characters.
+ * The limit depends on the font size, font face and bpp
+ * but with > 10,000 characters if you see issues probably you need to enable it.*/
+#define LV_FONT_FMT_TXT_LARGE 0
+
+/* Enables/disables support for compressed fonts. If it's disabled, compressed
+ * glyphs cannot be processed by the library and won't be rendered.
+ */
+#define LV_USE_FONT_COMPRESSED 1
+
+/* Enable subpixel rendering */
+#define LV_USE_FONT_SUBPX 0
+#if LV_USE_FONT_SUBPX
+/* Set the pixel order of the display.
+ * Important only if "subpx fonts" are used.
+ * With "normal" font it doesn't matter.
+ */
+#define LV_FONT_SUBPX_BGR 0
+#endif
+
+/*Declare the type of the user data of fonts (can be e.g. `void *`, `int`, `struct`)*/
+typedef void* lv_font_user_data_t;
+
+/*================
+ * THEME USAGE
+ *================*/
+
+/*Always enable at least on theme*/
+
+/* No theme, you can apply your styles as you need
+ * No flags. Set LV_THEME_DEFAULT_FLAG 0 */
+#define LV_USE_THEME_EMPTY 1
+
+/*Simple to the create your theme based on it
+ * No flags. Set LV_THEME_DEFAULT_FLAG 0 */
+#define LV_USE_THEME_TEMPLATE 0
+
+/* A fast and impressive theme.
+ * Flags:
+ * LV_THEME_MATERIAL_FLAG_LIGHT: light theme
+ * LV_THEME_MATERIAL_FLAG_DARK: dark theme
+ * LV_THEME_MATERIAL_FLAG_NO_TRANSITION: disable transitions (state change animations)
+ * LV_THEME_MATERIAL_FLAG_NO_FOCUS: disable indication of focused state)
+ * */
+#define LV_USE_THEME_MATERIAL 0
+
+/* Mono-color theme for monochrome displays.
+ * If LV_THEME_DEFAULT_COLOR_PRIMARY is LV_COLOR_BLACK the
+ * texts and borders will be black and the background will be
+ * white. Else the colors are inverted.
+ * No flags. Set LV_THEME_DEFAULT_FLAG 0 */
+#define LV_USE_THEME_MONO 0
+
+#define LV_THEME_DEFAULT_INCLUDE /*Include a header for the init. function*/
+#define LV_THEME_DEFAULT_INIT lv_theme_empty_init//lv_theme_material_init
+#define LV_THEME_DEFAULT_COLOR_PRIMARY lv_color_hex(0xffffff)
+#define LV_THEME_DEFAULT_COLOR_SECONDARY lv_color_hex(0xaaaaaa)
+#define LV_THEME_DEFAULT_FLAG 0//LV_THEME_MATERIAL_FLAG_DARK
+#define LV_THEME_DEFAULT_FONT_SMALL &jetbrains_mono_bold_20
+#define LV_THEME_DEFAULT_FONT_NORMAL &jetbrains_mono_bold_20
+#define LV_THEME_DEFAULT_FONT_SUBTITLE &jetbrains_mono_bold_20
+#define LV_THEME_DEFAULT_FONT_TITLE &jetbrains_mono_bold_20
+
+/*=================
+ * Text settings
+ *=================*/
+
+/* Select a character encoding for strings.
+ * Your IDE or editor should have the same character encoding
+ * - LV_TXT_ENC_UTF8
+ * - LV_TXT_ENC_ASCII
+ * */
+#define LV_TXT_ENC LV_TXT_ENC_UTF8
+
+/*Can break (wrap) texts on these chars*/
+#define LV_TXT_BREAK_CHARS " ,.;:-_"
+
+/* If a word is at least this long, will break wherever "prettiest"
+ * To disable, set to a value <= 0 */
+#define LV_TXT_LINE_BREAK_LONG_LEN 12
+
+/* Minimum number of characters in a long word to put on a line before a break.
+ * Depends on LV_TXT_LINE_BREAK_LONG_LEN. */
+#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3
+
+/* Minimum number of characters in a long word to put on a line after a break.
+ * Depends on LV_TXT_LINE_BREAK_LONG_LEN. */
+#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 3
+
+/* The control character to use for signalling text recoloring. */
+#define LV_TXT_COLOR_CMD "#"
+
+/* Support bidirectional texts.
+ * Allows mixing Left-to-Right and Right-to-Left texts.
+ * The direction will be processed according to the Unicode Bidirectioanl Algorithm:
+ * https://www.w3.org/International/articles/inline-bidi-markup/uba-basics*/
+#define LV_USE_BIDI 0
+#if LV_USE_BIDI
+/* Set the default direction. Supported values:
+ * `LV_BIDI_DIR_LTR` Left-to-Right
+ * `LV_BIDI_DIR_RTL` Right-to-Left
+ * `LV_BIDI_DIR_AUTO` detect texts base direction */
+#define LV_BIDI_BASE_DIR_DEF LV_BIDI_DIR_AUTO
+#endif
+
+/* Enable Arabic/Persian processing
+ * In these languages characters should be replaced with
+ * an other form based on their position in the text */
+#define LV_USE_ARABIC_PERSIAN_CHARS 0
+
+/*Change the built in (v)snprintf functions*/
+#define LV_SPRINTF_CUSTOM 0
+#if LV_SPRINTF_CUSTOM
+#define LV_SPRINTF_INCLUDE
+#define lv_snprintf snprintf
+#define lv_vsnprintf vsnprintf
+#else /*!LV_SPRINTF_CUSTOM*/
+#define LV_SPRINTF_DISABLE_FLOAT 1
+#endif /*LV_SPRINTF_CUSTOM*/
+
+/*===================
+ * LV_OBJ SETTINGS
+ *==================*/
+
+#if LV_USE_USER_DATA
+/*Declare the type of the user data of object (can be e.g. `void *`, `int`, `struct`)*/
+typedef void* lv_obj_user_data_t;
+/*Provide a function to free user data*/
+#define LV_USE_USER_DATA_FREE 0
+#if LV_USE_USER_DATA_FREE
+#define LV_USER_DATA_FREE_INCLUDE "something.h" /*Header for user data free function*/
+/* Function prototype : void user_data_free(lv_obj_t * obj); */
+#define LV_USER_DATA_FREE (user_data_free) /*Invoking for user data free function*/
+#endif
+#endif
+
+/*1: enable `lv_obj_realign()` based on `lv_obj_align()` parameters*/
+#define LV_USE_OBJ_REALIGN 1
+
+/* Enable to make the object clickable on a larger area.
+ * LV_EXT_CLICK_AREA_OFF or 0: Disable this feature
+ * LV_EXT_CLICK_AREA_TINY: The extra area can be adjusted horizontally and vertically (0..255 px)
+ * LV_EXT_CLICK_AREA_FULL: The extra area can be adjusted in all 4 directions (-32k..+32k px)
+ */
+#define LV_USE_EXT_CLICK_AREA LV_EXT_CLICK_AREA_TINY
+
+/*==================
+ * LV OBJ X USAGE
+ *================*/
+/*
+ * Documentation of the object types: https://docs.lvgl.com/#Object-types
+ */
+
+/*Arc (dependencies: -)*/
+#define LV_USE_ARC 1
+
+/*Bar (dependencies: -)*/
+#define LV_USE_BAR 1
+
+/*Button (dependencies: lv_cont*/
+#define LV_USE_BTN 1
+
+/*Button matrix (dependencies: -)*/
+#define LV_USE_BTNMATRIX 1
+
+/*Calendar (dependencies: -)*/
+#define LV_USE_CALENDAR 1
+#if LV_USE_CALENDAR
+#define LV_CALENDAR_WEEK_STARTS_MONDAY 0
+#endif
+
+/*Canvas (dependencies: lv_img)*/
+#define LV_USE_CANVAS 0
+
+/*Check box (dependencies: lv_btn, lv_label)*/
+#define LV_USE_CHECKBOX 1
+
+/*Chart (dependencies: -)*/
+#define LV_USE_CHART 1
+#if LV_USE_CHART
+#define LV_CHART_AXIS_TICK_LABEL_MAX_LEN 256
+#endif
+
+/*Container (dependencies: -*/
+#define LV_USE_CONT 1
+
+/*Color picker (dependencies: -*/
+#define LV_USE_CPICKER 0
+
+/*Drop down list (dependencies: lv_page, lv_label, lv_symbol_def.h)*/
+#define LV_USE_DROPDOWN 1
+#if LV_USE_DROPDOWN != 0
+/*Open and close default animation time [ms] (0: no animation)*/
+#define LV_DROPDOWN_DEF_ANIM_TIME 200
+#endif
+
+/*Gauge (dependencies:lv_bar, lv_linemeter)*/
+#define LV_USE_GAUGE 1
+
+/*Image (dependencies: lv_label*/
+#define LV_USE_IMG 1
+
+/*Image Button (dependencies: lv_btn*/
+#define LV_USE_IMGBTN 1
+#if LV_USE_IMGBTN
+/*1: The imgbtn requires left, mid and right parts and the width can be set freely*/
+#define LV_IMGBTN_TILED 0
+#endif
+
+/*Keyboard (dependencies: lv_btnm)*/
+#define LV_USE_KEYBOARD 0
+
+/*Label (dependencies: -*/
+#define LV_USE_LABEL 1
+#if LV_USE_LABEL != 0
+/*Hor, or ver. scroll speed [px/sec] in 'LV_LABEL_LONG_ROLL/ROLL_CIRC' mode*/
+#define LV_LABEL_DEF_SCROLL_SPEED 25
+
+/* Waiting period at beginning/end of animation cycle */
+#define LV_LABEL_WAIT_CHAR_COUNT 3
+
+/*Enable selecting text of the label */
+#define LV_LABEL_TEXT_SEL 0
+
+/*Store extra some info in labels (12 bytes) to speed up drawing of very long texts*/
+#define LV_LABEL_LONG_TXT_HINT 0
+#endif
+
+/*LED (dependencies: -)*/
+#define LV_USE_LED 0
+#if LV_USE_LED
+#define LV_LED_BRIGHT_MIN 120 /*Minimal brightness*/
+#define LV_LED_BRIGHT_MAX 255 /*Maximal brightness*/
+#endif
+
+/*Line (dependencies: -*/
+#define LV_USE_LINE 1
+
+/*List (dependencies: lv_page, lv_btn, lv_label, (lv_img optionally for icons ))*/
+#define LV_USE_LIST 1
+#if LV_USE_LIST != 0
+/*Default animation time of focusing to a list element [ms] (0: no animation) */
+#define LV_LIST_DEF_ANIM_TIME 100
+#endif
+
+/*Line meter (dependencies: *;)*/
+#define LV_USE_LINEMETER 1
+#if LV_USE_LINEMETER
+/* Draw line more precisely at cost of performance.
+ * Useful if there are lot of lines any minor are visible
+ * 0: No extra precision
+ * 1: Some extra precision
+ * 2: Best precision
+ */
+#define LV_LINEMETER_PRECISE 0
+#endif
+
+/*Mask (dependencies: -)*/
+#define LV_USE_OBJMASK 0
+
+/*Message box (dependencies: lv_rect, lv_btnm, lv_label)*/
+#define LV_USE_MSGBOX 0
+
+/*Page (dependencies: lv_cont)*/
+#define LV_USE_PAGE 1
+#if LV_USE_PAGE != 0
+/*Focus default animation time [ms] (0: no animation)*/
+#define LV_PAGE_DEF_ANIM_TIME 400
+#endif
+
+/*Preload (dependencies: lv_arc, lv_anim)*/
+#define LV_USE_SPINNER 0
+#if LV_USE_SPINNER != 0
+#define LV_SPINNER_DEF_ARC_LENGTH 60 /*[deg]*/
+#define LV_SPINNER_DEF_SPIN_TIME 1000 /*[ms]*/
+#define LV_SPINNER_DEF_ANIM LV_SPINNER_TYPE_SPINNING_ARC
+#endif
+
+/*Roller (dependencies: lv_ddlist)*/
+#define LV_USE_ROLLER 1
+#if LV_USE_ROLLER != 0
+/*Focus animation time [ms] (0: no animation)*/
+#define LV_ROLLER_DEF_ANIM_TIME 200
+
+/*Number of extra "pages" when the roller is infinite*/
+#define LV_ROLLER_INF_PAGES 7
+#endif
+
+/*Slider (dependencies: lv_bar)*/
+#define LV_USE_SLIDER 1
+
+/*Spinbox (dependencies: lv_ta)*/
+#define LV_USE_SPINBOX 0
+
+/*Switch (dependencies: lv_slider)*/
+#define LV_USE_SWITCH 1
+
+/*Text area (dependencies: lv_label, lv_page)*/
+#define LV_USE_TEXTAREA 1
+#if LV_USE_TEXTAREA != 0
+#define LV_TEXTAREA_DEF_CURSOR_BLINK_TIME 400 /*ms*/
+#define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500 /*ms*/
+#endif
+
+/*Table (dependencies: lv_label)*/
+#define LV_USE_TABLE 1
+#if LV_USE_TABLE
+#define LV_TABLE_COL_MAX 12
+#define LV_TABLE_CELL_STYLE_CNT 5
+#endif
+
+
+/*Tab (dependencies: lv_page, lv_btnm)*/
+#define LV_USE_TABVIEW 0
+# if LV_USE_TABVIEW != 0
+/*Time of slide animation [ms] (0: no animation)*/
+#define LV_TABVIEW_DEF_ANIM_TIME 300
+#endif
+
+/*Tileview (dependencies: lv_page) */
+#define LV_USE_TILEVIEW 0
+#if LV_USE_TILEVIEW
+/*Time of slide animation [ms] (0: no animation)*/
+#define LV_TILEVIEW_DEF_ANIM_TIME 300
+#endif
+
+/*Window (dependencies: lv_cont, lv_btn, lv_label, lv_img, lv_page)*/
+#define LV_USE_WIN 0
+
+/*==================
+ * Non-user section
+ *==================*/
+
+#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) /* Disable warnings for Visual Studio*/
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+
+/*--END OF LV_CONF_H--*/
+
+#endif /*LV_CONF_H*/
diff --git a/lv_drivers b/lv_drivers
new file mode 160000
index 0000000..8c96359
--- /dev/null
+++ b/lv_drivers
@@ -0,0 +1 @@
+Subproject commit 8c96359f4199297809ab205870eb603b03d98b4e
diff --git a/lv_drv_conf.h b/lv_drv_conf.h
new file mode 100644
index 0000000..391f708
--- /dev/null
+++ b/lv_drv_conf.h
@@ -0,0 +1,410 @@
+/**
+ * @file lv_drv_conf.h
+ *
+ */
+
+/*
+ * COPY THIS FILE AS lv_drv_conf.h
+ */
+
+#if 1 /*Set it to "1" to enable the content*/
+
+#ifndef LV_DRV_CONF_H
+#define LV_DRV_CONF_H
+
+#include "lv_conf.h"
+
+/*********************
+ * DELAY INTERFACE
+ *********************/
+#define LV_DRV_DELAY_INCLUDE /*Dummy include by default*/
+#define LV_DRV_DELAY_US(us) /*delay_us(us)*/ /*Delay the given number of microseconds*/
+#define LV_DRV_DELAY_MS(ms) /*delay_ms(ms)*/ /*Delay the given number of milliseconds*/
+
+/*********************
+ * DISPLAY INTERFACE
+ *********************/
+
+/*------------
+ * Common
+ *------------*/
+#define LV_DRV_DISP_INCLUDE /*Dummy include by default*/
+#define LV_DRV_DISP_CMD_DATA(val) /*pin_x_set(val)*/ /*Set the command/data pin to 'val'*/
+#define LV_DRV_DISP_RST(val) /*pin_x_set(val)*/ /*Set the reset pin to 'val'*/
+
+/*---------
+ * SPI
+ *---------*/
+#define LV_DRV_DISP_SPI_CS(val) /*spi_cs_set(val)*/ /*Set the SPI's Chip select to 'val'*/
+#define LV_DRV_DISP_SPI_WR_BYTE(data) /*spi_wr(data)*/ /*Write a byte the SPI bus*/
+#define LV_DRV_DISP_SPI_WR_ARRAY(adr, n) /*spi_wr_mem(adr, n)*/ /*Write 'n' bytes to SPI bus from 'adr'*/
+
+/*------------------
+ * Parallel port
+ *-----------------*/
+#define LV_DRV_DISP_PAR_CS(val) /*par_cs_set(val)*/ /*Set the Parallel port's Chip select to 'val'*/
+#define LV_DRV_DISP_PAR_SLOW /*par_slow()*/ /*Set low speed on the parallel port*/
+#define LV_DRV_DISP_PAR_FAST /*par_fast()*/ /*Set high speed on the parallel port*/
+#define LV_DRV_DISP_PAR_WR_WORD(data) /*par_wr(data)*/ /*Write a word to the parallel port*/
+#define LV_DRV_DISP_PAR_WR_ARRAY(adr, n) /*par_wr_mem(adr,n)*/ /*Write 'n' bytes to Parallel ports from 'adr'*/
+
+/***************************
+ * INPUT DEVICE INTERFACE
+ ***************************/
+
+/*----------
+ * Common
+ *----------*/
+#define LV_DRV_INDEV_INCLUDE /*Dummy include by default*/
+#define LV_DRV_INDEV_RST(val) /*pin_x_set(val)*/ /*Set the reset pin to 'val'*/
+#define LV_DRV_INDEV_IRQ_READ 0 /*pn_x_read()*/ /*Read the IRQ pin*/
+
+/*---------
+ * SPI
+ *---------*/
+#define LV_DRV_INDEV_SPI_CS(val) /*spi_cs_set(val)*/ /*Set the SPI's Chip select to 'val'*/
+#define LV_DRV_INDEV_SPI_XCHG_BYTE(data) 0 /*spi_xchg(val)*/ /*Write 'val' to SPI and give the read value*/
+
+/*---------
+ * I2C
+ *---------*/
+#define LV_DRV_INDEV_I2C_START /*i2c_start()*/ /*Make an I2C start*/
+#define LV_DRV_INDEV_I2C_STOP /*i2c_stop()*/ /*Make an I2C stop*/
+#define LV_DRV_INDEV_I2C_RESTART /*i2c_restart()*/ /*Make an I2C restart*/
+#define LV_DRV_INDEV_I2C_WR(data) /*i2c_wr(data)*/ /*Write a byte to the I1C bus*/
+#define LV_DRV_INDEV_I2C_READ(last_read) 0 /*i2c_rd()*/ /*Read a byte from the I2C bud*/
+
+
+/*********************
+ * DISPLAY DRIVERS
+ *********************/
+#ifndef USE_WAYLAND
+# define USE_WAYLAND 0
+#endif
+
+#if USE_WAYLAND
+# define WAYLAND_HOR_RES 480
+# define WAYLAND_VER_RES 320
+# define WAYLAND_SURF_TITLE "LVGL"
+#endif
+/*-------------------
+ * Monitor of PC
+ *-------------------*/
+#ifndef USE_MONITOR
+# define USE_MONITOR 1
+#endif
+
+#if USE_MONITOR
+# define MONITOR_HOR_RES 240
+# define MONITOR_VER_RES 240
+
+/* Scale window by this factor (useful when simulating small screens) */
+# define MONITOR_ZOOM 1
+
+/* Used to test true double buffering with only address changing.
+ * Set LV_draw_buf_SIZE = (LV_HOR_RES * LV_VER_RES) and LV_draw_buf_DOUBLE = 1 and LV_COLOR_DEPTH = 32" */
+# define MONITOR_DOUBLE_BUFFERED 0
+
+/*Eclipse: Visual Studio: */
+# define MONITOR_SDL_INCLUDE_PATH
+
+/*Different rendering might be used if running in a Virtual machine*/
+# define MONITOR_VIRTUAL_MACHINE 0
+
+/*Open two windows to test multi display support*/
+# define MONITOR_DUAL 0
+#endif
+
+/*-----------------------------------
+ * Native Windows (including mouse)
+ *----------------------------------*/
+#ifndef USE_WINDOWS
+# define USE_WINDOWS 0
+#endif
+
+#define USE_WINDOWS 0
+#if USE_WINDOWS
+# define WINDOW_HOR_RES 480
+# define WINDOW_VER_RES 320
+#endif
+
+/*----------------------------------------
+ * GTK drivers (monitor, mouse, keyboard
+ *---------------------------------------*/
+#ifndef USE_GTK
+# define USE_GTK 0
+#endif
+
+/*----------------
+ * SSD1963
+ *--------------*/
+#ifndef USE_SSD1963
+# define USE_SSD1963 0
+#endif
+
+#if USE_SSD1963
+# define SSD1963_HOR_RES LV_HOR_RES
+# define SSD1963_VER_RES LV_VER_RES
+# define SSD1963_HT 531
+# define SSD1963_HPS 43
+# define SSD1963_LPS 8
+# define SSD1963_HPW 10
+# define SSD1963_VT 288
+# define SSD1963_VPS 12
+# define SSD1963_FPS 4
+# define SSD1963_VPW 10
+# define SSD1963_HS_NEG 0 /*Negative hsync*/
+# define SSD1963_VS_NEG 0 /*Negative vsync*/
+# define SSD1963_ORI 0 /*0, 90, 180, 270*/
+# define SSD1963_COLOR_DEPTH 16
+#endif
+
+/*----------------
+ * R61581
+ *--------------*/
+#ifndef USE_R61581
+# define USE_R61581 0
+#endif
+
+#if USE_R61581
+# define R61581_HOR_RES LV_HOR_RES
+# define R61581_VER_RES LV_VER_RES
+# define R61581_HSPL 0 /*HSYNC signal polarity*/
+# define R61581_HSL 10 /*HSYNC length (Not Implemented)*/
+# define R61581_HFP 10 /*Horitontal Front poarch (Not Implemented)*/
+# define R61581_HBP 10 /*Horitontal Back poarch (Not Implemented */
+# define R61581_VSPL 0 /*VSYNC signal polarity*/
+# define R61581_VSL 10 /*VSYNC length (Not Implemented)*/
+# define R61581_VFP 8 /*Vertical Front poarch*/
+# define R61581_VBP 8 /*Vertical Back poarch */
+# define R61581_DPL 0 /*DCLK signal polarity*/
+# define R61581_EPL 1 /*ENABLE signal polarity*/
+# define R61581_ORI 0 /*0, 180*/
+# define R61581_LV_COLOR_DEPTH 16 /*Fix 16 bit*/
+#endif
+
+/*------------------------------
+ * ST7565 (Monochrome, low res.)
+ *-----------------------------*/
+#ifndef USE_ST7565
+# define USE_ST7565 0
+#endif
+
+#if USE_ST7565
+/*No settings*/
+#endif /*USE_ST7565*/
+
+/*------------------------------
+ * GC9A01 (color, low res.)
+ *-----------------------------*/
+#ifndef USE_GC9A01
+# define USE_GC9A01 0
+#endif
+
+#if USE_GC9A01
+/*No settings*/
+#endif /*USE_GC9A01*/
+
+/*------------------------------------------
+ * UC1610 (4 gray 160*[104|128])
+ * (EA DOGXL160 160x104 tested)
+ *-----------------------------------------*/
+#ifndef USE_UC1610
+# define USE_UC1610 0
+#endif
+
+#if USE_UC1610
+# define UC1610_HOR_RES LV_HOR_RES
+# define UC1610_VER_RES LV_VER_RES
+# define UC1610_INIT_CONTRAST 33 /* init contrast, values in [%] */
+# define UC1610_INIT_HARD_RST 0 /* 1 : hardware reset at init, 0 : software reset */
+# define UC1610_TOP_VIEW 0 /* 0 : Bottom View, 1 : Top View */
+#endif /*USE_UC1610*/
+
+/*-------------------------------------------------
+ * SHARP memory in pixel monochrome display series
+ * LS012B7DD01 (184x38 pixels.)
+ * LS013B7DH03 (128x128 pixels.)
+ * LS013B7DH05 (144x168 pixels.)
+ * LS027B7DH01 (400x240 pixels.) (tested)
+ * LS032B7DD02 (336x536 pixels.)
+ * LS044Q7DH01 (320x240 pixels.)
+ *------------------------------------------------*/
+#ifndef USE_SHARP_MIP
+# define USE_SHARP_MIP 0
+#endif
+
+#if USE_SHARP_MIP
+# define SHARP_MIP_HOR_RES LV_HOR_RES
+# define SHARP_MIP_VER_RES LV_VER_RES
+# define SHARP_MIP_SOFT_COM_INVERSION 0
+# define SHARP_MIP_REV_BYTE(b) /*((uint8_t) __REV(__RBIT(b)))*/ /*Architecture / compiler dependent byte bits order reverse*/
+#endif /*USE_SHARP_MIP*/
+
+/*-------------------------------------------------
+ * ILI9341 240X320 TFT LCD
+ *------------------------------------------------*/
+#ifndef USE_ILI9341
+# define USE_ILI9341 0
+#endif
+
+#if USE_ILI9341
+# define ILI9341_HOR_RES LV_HOR_RES
+# define ILI9341_VER_RES LV_VER_RES
+# define ILI9341_GAMMA 1
+# define ILI9341_TEARING 0
+#endif /*USE_ILI9341*/
+
+/*-----------------------------------------
+ * Linux frame buffer device (/dev/fbx)
+ *-----------------------------------------*/
+#ifndef USE_FBDEV
+# define USE_FBDEV 1
+#endif
+
+#if USE_FBDEV
+# define FBDEV_PATH "/dev/fb0"
+#endif
+
+/*-----------------------------------------
+ * FreeBSD frame buffer device (/dev/fbx)
+ *.........................................*/
+#ifndef USE_BSD_FBDEV
+# define USE_BSD_FBDEV 0
+#endif
+
+#if USE_BSD_FBDEV
+# define FBDEV_PATH "/dev/fb0"
+#endif
+
+/*-----------------------------------------
+ * DRM/KMS device (/dev/dri/cardX)
+ *-----------------------------------------*/
+#ifndef USE_DRM
+# define USE_DRM 0
+#endif
+
+#if USE_DRM
+# define DRM_CARD "/dev/dri/card0"
+# define DRM_CONNECTOR_ID -1 /* -1 for the first connected one */
+#endif
+
+/*********************
+ * INPUT DEVICES
+ *********************/
+
+/*--------------
+ * XPT2046
+ *--------------*/
+#ifndef USE_XPT2046
+# define USE_XPT2046 0
+#endif
+
+#if USE_XPT2046
+# define XPT2046_HOR_RES 480
+# define XPT2046_VER_RES 320
+# define XPT2046_X_MIN 200
+# define XPT2046_Y_MIN 200
+# define XPT2046_X_MAX 3800
+# define XPT2046_Y_MAX 3800
+# define XPT2046_AVG 4
+# define XPT2046_X_INV 0
+# define XPT2046_Y_INV 0
+# define XPT2046_XY_SWAP 0
+#endif
+
+/*-----------------
+ * FT5406EE8
+ *-----------------*/
+#ifndef USE_FT5406EE8
+# define USE_FT5406EE8 0
+#endif
+
+#if USE_FT5406EE8
+# define FT5406EE8_I2C_ADR 0x38 /*7 bit address*/
+#endif
+
+/*---------------
+ * AD TOUCH
+ *--------------*/
+#ifndef USE_AD_TOUCH
+# define USE_AD_TOUCH 0
+#endif
+
+#if USE_AD_TOUCH
+/*No settings*/
+#endif
+
+
+/*---------------------------------------
+ * Mouse or touchpad on PC (using SDL)
+ *-------------------------------------*/
+#ifndef USE_MOUSE
+# define USE_MOUSE 1
+#endif
+
+#if USE_MOUSE
+/*No settings*/
+#endif
+
+/*-------------------------------------------
+ * Mousewheel as encoder on PC (using SDL)
+ *------------------------------------------*/
+#ifndef USE_MOUSEWHEEL
+# define USE_MOUSEWHEEL 1
+#endif
+
+#if USE_MOUSEWHEEL
+/*No settings*/
+#endif
+
+/*-------------------------------------------------
+ * Touchscreen as libinput interface (for Linux based systems)
+ *------------------------------------------------*/
+#ifndef USE_LIBINPUT
+# define USE_LIBINPUT 0
+#endif
+
+#if USE_LIBINPUT
+# define LIBINPUT_NAME "/dev/input/event0" /*You can use the "evtest" Linux tool to get the list of devices and test them*/
+#endif /*USE_LIBINPUT*/
+
+/*-------------------------------------------------
+ * Mouse or touchpad as evdev interface (for Linux based systems)
+ *------------------------------------------------*/
+#ifndef USE_EVDEV
+# define USE_EVDEV 0
+#endif
+
+#ifndef USE_BSD_EVDEV
+# define USE_BSD_EVDEV 0
+#endif
+
+#if USE_EVDEV || USE_BSD_EVDEV
+# define EVDEV_NAME "/dev/input/event0" /*You can use the "evtest" Linux tool to get the list of devices and test them*/
+# define EVDEV_SWAP_AXES 0 /*Swap the x and y axes of the touchscreen*/
+
+# define EVDEV_CALIBRATE 0 /*Scale and offset the touchscreen coordinates by using maximum and minimum values for each axis*/
+
+# if EVDEV_CALIBRATE
+# define EVDEV_HOR_MIN 0 /*to invert axis swap EVDEV_XXX_MIN by EVDEV_XXX_MAX*/
+# define EVDEV_HOR_MAX 4096 /*"evtest" Linux tool can help to get the correct calibraion values>*/
+# define EVDEV_VER_MIN 0
+# define EVDEV_VER_MAX 4096
+# endif /*EVDEV_CALIBRATE*/
+#endif /*USE_EVDEV*/
+
+/*-------------------------------
+ * Keyboard of a PC (using SDL)
+ *------------------------------*/
+#ifndef USE_KEYBOARD
+# define USE_KEYBOARD 1
+#endif
+
+#if USE_KEYBOARD
+/*No settings*/
+#endif
+
+#endif /*LV_DRV_CONF_H*/
+
+#endif /*End of "Content enable"*/
diff --git a/main.cpp b/main.cpp
new file mode 100644
index 0000000..4aee494
--- /dev/null
+++ b/main.cpp
@@ -0,0 +1,783 @@
+
+/**
+ * @file main
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#define _DEFAULT_SOURCE /* needed for usleep() */
+#include
+#include
+#define SDL_MAIN_HANDLED /*To fix SDL's "undefined reference to WinMain" issue*/
+#include
+#include "lvgl/lvgl.h"
+//#include "lvgl/examples/lv_examples.h"
+//#include "lv_demos/lv_demo.h"
+#include "lv_drivers/display/monitor.h"
+#include "lv_drivers/indev/mouse.h"
+#include "lv_drivers/indev/keyboard.h"
+#include "lv_drivers/indev/mousewheel.h"
+
+// get PineTime header
+#include "displayapp/lv_pinetime_theme.h"
+#include
+#include
+
+#include "BootloaderVersion.h"
+#include "components/battery/BatteryController.h"
+#include "components/ble/BleController.h"
+#include "components/ble/NotificationManager.h"
+#include "components/brightness/BrightnessController.h"
+#include "components/motor/MotorController.h"
+#include "components/datetime/DateTimeController.h"
+#include "components/heartrate/HeartRateController.h"
+#include "components/fs/FS.h"
+#include "drivers/Spi.h"
+#include "drivers/SpiMaster.h"
+#include "drivers/SpiNorFlash.h"
+#include "drivers/St7789.h"
+#include "drivers/TwiMaster.h"
+#include "drivers/Cst816s.h"
+#include "drivers/PinMap.h"
+#include "systemtask/SystemTask.h"
+#include "drivers/PinMap.h"
+#include "touchhandler/TouchHandler.h"
+#include "buttonhandler/ButtonHandler.h"
+
+// get the simulator-headers
+#include "displayapp/DisplayApp.h"
+#include "displayapp/LittleVgl.h"
+
+#include
+
+#include
+#include
+#include
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+static void hal_init(void);
+static int tick_thread(void *data);
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+lv_indev_t *kb_indev;
+lv_indev_t *mouse_indev = nullptr;
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+void nrfx_gpiote_evt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) {}
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * VARIABLES
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+constexpr NRF_TWIM_Type *NRF_TWIM1 = nullptr;
+
+
+static constexpr uint8_t touchPanelTwiAddress = 0x15;
+static constexpr uint8_t motionSensorTwiAddress = 0x18;
+static constexpr uint8_t heartRateSensorTwiAddress = 0x44;
+
+Pinetime::Drivers::SpiMaster spi {Pinetime::Drivers::SpiMaster::SpiModule::SPI0,
+ {Pinetime::Drivers::SpiMaster::BitOrder::Msb_Lsb,
+ Pinetime::Drivers::SpiMaster::Modes::Mode3,
+ Pinetime::Drivers::SpiMaster::Frequencies::Freq8Mhz,
+ Pinetime::PinMap::SpiSck,
+ Pinetime::PinMap::SpiMosi,
+ Pinetime::PinMap::SpiMiso}};
+
+Pinetime::Drivers::Spi lcdSpi {spi, Pinetime::PinMap::SpiLcdCsn};
+Pinetime::Drivers::St7789 lcd {lcdSpi, Pinetime::PinMap::LcdDataCommand};
+
+Pinetime::Drivers::Spi flashSpi {spi, Pinetime::PinMap::SpiFlashCsn};
+Pinetime::Drivers::SpiNorFlash spiNorFlash {flashSpi};
+
+// The TWI device should work @ up to 400Khz but there is a HW bug which prevent it from
+// respecting correct timings. According to erratas heet, this magic value makes it run
+// at ~390Khz with correct timings.
+static constexpr uint32_t MaxTwiFrequencyWithoutHardwareBug {0x06200000};
+Pinetime::Drivers::TwiMaster twiMaster {NRF_TWIM1, MaxTwiFrequencyWithoutHardwareBug, Pinetime::PinMap::TwiSda, Pinetime::PinMap::TwiScl};
+Pinetime::Drivers::Cst816S touchPanel; // {twiMaster, touchPanelTwiAddress};
+//#ifdef PINETIME_IS_RECOVERY
+// #include "displayapp/DummyLittleVgl.h"
+// #include "displayapp/DisplayAppRecovery.h"
+//#else
+// #include "displayapp/LittleVgl.h"
+// #include "displayapp/DisplayApp.h"
+//#endif
+Pinetime::Components::LittleVgl lvgl {lcd, touchPanel};
+
+Pinetime::Drivers::Bma421 motionSensor {twiMaster, motionSensorTwiAddress};
+Pinetime::Drivers::Hrs3300 heartRateSensor {twiMaster, heartRateSensorTwiAddress};
+
+TimerHandle_t debounceTimer;
+TimerHandle_t debounceChargeTimer;
+Pinetime::Controllers::Battery batteryController;
+Pinetime::Controllers::Ble bleController;
+
+Pinetime::Controllers::HeartRateController heartRateController;
+Pinetime::Applications::HeartRateTask heartRateApp(heartRateSensor, heartRateController);
+
+Pinetime::Controllers::FS fs; // {spiNorFlash};
+Pinetime::Controllers::Settings settingsController {fs};
+Pinetime::Controllers::MotorController motorController {};
+
+Pinetime::Controllers::DateTime dateTimeController {settingsController};
+Pinetime::Drivers::Watchdog watchdog;
+Pinetime::Drivers::WatchdogView watchdogView(watchdog);
+Pinetime::Controllers::NotificationManager notificationManager;
+Pinetime::Controllers::MotionController motionController;
+Pinetime::Controllers::TimerController timerController;
+Pinetime::Controllers::AlarmController alarmController {dateTimeController};
+Pinetime::Controllers::TouchHandler touchHandler(touchPanel, lvgl);
+Pinetime::Controllers::ButtonHandler buttonHandler;
+Pinetime::Controllers::BrightnessController brightnessController {};
+
+Pinetime::Applications::DisplayApp displayApp(lcd,
+ lvgl,
+ touchPanel,
+ batteryController,
+ bleController,
+ dateTimeController,
+ watchdogView,
+ notificationManager,
+ heartRateController,
+ settingsController,
+ motorController,
+ motionController,
+ timerController,
+ alarmController,
+ brightnessController,
+ touchHandler);
+
+Pinetime::System::SystemTask systemTask(spi,
+ lcd,
+ spiNorFlash,
+ twiMaster,
+ touchPanel,
+ lvgl,
+ batteryController,
+ bleController,
+ dateTimeController,
+ timerController,
+ alarmController,
+ watchdog,
+ notificationManager,
+ motorController,
+ heartRateSensor,
+ motionController,
+ motionSensor,
+ settingsController,
+ heartRateController,
+ displayApp,
+ heartRateApp,
+ fs,
+ touchHandler,
+ buttonHandler);
+
+// variable used in SystemTask.cpp Work loop
+std::chrono::time_point NoInit_BackUpTime;
+
+class Framework {
+public:
+ // Contructor which initialize the parameters.
+ Framework(bool visible_, int height_, int width_) :
+ visible(visible_), height(height_), width(width_)
+ {
+ if (visible) {
+ //SDL_Init(SDL_INIT_VIDEO); // Initializing SDL as Video
+ SDL_CreateWindowAndRenderer(width, height, 0, &window, &renderer);
+ SDL_SetWindowTitle(window, "LV Simulator Status");
+ { // move window a bit to the right, to not be over the PineTime Screen
+ int x,y;
+ SDL_GetWindowPosition(window, &x, &y);
+ SDL_SetWindowPosition(window, x+LV_HOR_RES_MAX, y);
+ }
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0); // setting draw color
+ SDL_RenderClear(renderer); // Clear the newly created window
+ SDL_RenderPresent(renderer); // Reflects the changes done in the
+ // window.
+ }
+ motorController.Init();
+ settingsController.Init();
+
+ lvgl.Init();
+
+ lv_mem_monitor(&mem_mon);
+ printf("initial free_size = %u\n", mem_mon.free_size);
+
+ // update time to current system time once on startup
+ dateTimeController.SetCurrentTime(std::chrono::system_clock::now());
+
+ systemTask.Start();
+
+ // initialize the first LVGL screen
+ //const auto clockface = settingsController.GetClockFace();
+ //switch_to_screen(1+clockface);
+ }
+
+ // Destructor
+ ~Framework(){
+ SDL_DestroyRenderer(renderer);
+ SDL_DestroyWindow(window);
+ SDL_Quit();
+ }
+
+ void draw_circle_red(int center_x, int center_y, int radius_){
+ SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
+ draw_circle_(center_x, center_y, radius_);
+ }
+ void draw_circle_green(int center_x, int center_y, int radius_){
+ SDL_SetRenderDrawColor(renderer, 0, 255, 0, 255);
+ draw_circle_(center_x, center_y, radius_);
+ }
+ void draw_circle_blue(int center_x, int center_y, int radius_){
+ SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
+ draw_circle_(center_x, center_y, radius_);
+ }
+ void draw_circle_yellow(int center_x, int center_y, int radius_){
+ SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255);
+ draw_circle_(center_x, center_y, radius_);
+ }
+ void draw_circle_grey(int center_x, int center_y, int radius_){
+ SDL_SetRenderDrawColor(renderer, 128, 128, 128, 255);
+ draw_circle_(center_x, center_y, radius_);
+ }
+ void draw_circle_white(int center_x, int center_y, int radius_){
+ SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
+ draw_circle_(center_x, center_y, radius_);
+ }
+ void draw_circle_(int center_x, int center_y, int radius_){
+ // Drawing circle
+ for(int x=center_x-radius_; x<=center_x+radius_; x++){
+ for(int y=center_y-radius_; y<=center_y+radius_; y++){
+ if((std::pow(center_y-y,2)+std::pow(center_x-x,2)) <=
+ std::pow(radius_,2)){
+ SDL_RenderDrawPoint(renderer, x, y);
+ }
+ }
+ }
+ }
+
+ void refresh() {
+ // always refresh the LVGL screen
+ this->refresh_screen();
+
+ if (!visible) {
+ return;
+ }
+ SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
+ SDL_RenderClear(renderer);
+ { // motorController.is_ringing
+ constexpr const int center_x = 15;
+ constexpr const int center_y = 15;
+ if (motorController.is_ringing) {
+ draw_circle_red(center_x, center_y, 15);
+ } else {
+ draw_circle_grey(center_x, center_y, 15);
+ }
+ }
+ { // motorController.motor_is_running
+ constexpr const int center_x = 45;
+ constexpr const int center_y = 15;
+ if (motorController.motor_is_running) {
+ draw_circle_red(center_x, center_y, 15);
+ } else {
+ draw_circle_grey(center_x, center_y, 15);
+ }
+ }
+ { // ble.motor_is_running
+ constexpr const int center_x = 75;
+ constexpr const int center_y = 15;
+ if (bleController.IsConnected()) {
+ draw_circle_blue(center_x, center_y, 15);
+ } else {
+ draw_circle_grey(center_x, center_y, 15);
+ }
+ }
+ // batteryController.percentRemaining
+ for (uint8_t percent=0; percent<=10; percent++) {
+ const int center_x = 15+15*percent;
+ const int center_y = 60;
+ if (batteryController.percentRemaining < percent*10) {
+ draw_circle_grey(center_x, center_y, 15);
+ } else {
+ draw_circle_green(center_x, center_y, 15);
+ }
+ }
+ { // batteryController.isCharging
+ constexpr const int center_x = 15;
+ constexpr const int center_y = 90;
+ if (batteryController.isCharging) {
+ draw_circle_yellow(center_x, center_y, 15);
+ } else
+ {
+ draw_circle_grey(center_x, center_y, 15);
+ }
+ }
+ { // brightnessController.Level
+ constexpr const int center_y = 15;
+ const Pinetime::Controllers::BrightnessController::Levels level = brightnessController.Level();
+ uint8_t level_idx = 0;
+ if (level == Pinetime::Controllers::BrightnessController::Levels::Low)
+ {
+ level_idx = 1;
+ } else if (level == Pinetime::Controllers::BrightnessController::Levels::Medium)
+ {
+ level_idx = 2;
+ } else if (level == Pinetime::Controllers::BrightnessController::Levels::High)
+ {
+ level_idx = 3;
+ }
+ for (uint8_t i=0; i<4; i++) {
+ const int center_x = 115+15*i;
+ if (i <= level_idx) {
+ draw_circle_white(center_x, center_y, 15);
+ } else {
+ draw_circle_grey(center_x, center_y, 15);
+ }
+ }
+ }
+ // Show the change on the screen
+ SDL_RenderPresent(renderer);
+ }
+
+ void send_notification() {
+ Pinetime::Controllers::NotificationManager::Notification notif;
+ const std::string message("Lorem Ipsum");
+ std::copy(message.begin(), message.end(), notif.message.data());
+ notif.message[message.size() - 1] = '\0';
+ notif.size = message.size();
+ notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert;
+ notificationManager.Push(std::move(notif));
+ }
+
+ // can't use SDL_PollEvent, as those are fed to lvgl
+ // implement a non-descructive key-pressed handler (as not consuming SDL_Events)
+ void handle_keys() {
+ const Uint8 *state = SDL_GetKeyboardState(NULL);
+ const bool key_shift = state[SDL_SCANCODE_LSHIFT] || state[SDL_SCANCODE_RSHIFT];
+ auto debounce = [&] (const char key_low, const char key_capital, const bool scancode, bool &key_handled) {
+ if (scancode && !key_handled) {
+ if (key_shift) {
+ this->handle_key(key_capital);
+ } else {
+ this->handle_key(key_low);
+ }
+ key_handled = true;
+ } else if (scancode && key_handled) {
+ // ignore, already handled
+ } else {
+ key_handled = false;
+ }
+ };
+ debounce('r', 'R', state[SDL_SCANCODE_R], key_handled_r);
+ debounce('n', 'N', state[SDL_SCANCODE_N], key_handled_n);
+ debounce('m', 'M', state[SDL_SCANCODE_M], key_handled_m);
+ debounce('b', 'B', state[SDL_SCANCODE_B], key_handled_b);
+ debounce('v', 'V', state[SDL_SCANCODE_V], key_handled_v);
+ debounce('c', 'C', state[SDL_SCANCODE_C], key_handled_c);
+ debounce('l', 'L', state[SDL_SCANCODE_L], key_handled_l);
+ debounce('p', 'P', state[SDL_SCANCODE_P], key_handled_p);
+ debounce('s', 'S', state[SDL_SCANCODE_S], key_handled_s);
+ debounce('h', 'H', state[SDL_SCANCODE_H], key_handled_h);
+ // screen switcher buttons
+ debounce('1', '!'+1, state[SDL_SCANCODE_1], key_handled_1);
+ debounce('2', '!'+2, state[SDL_SCANCODE_2], key_handled_2);
+ debounce('3', '!'+3, state[SDL_SCANCODE_3], key_handled_3);
+ debounce('4', '!'+4, state[SDL_SCANCODE_4], key_handled_4);
+ debounce('5', '!'+5, state[SDL_SCANCODE_5], key_handled_5);
+ debounce('6', '!'+6, state[SDL_SCANCODE_6], key_handled_6);
+ debounce('7', '!'+7, state[SDL_SCANCODE_7], key_handled_7);
+ debounce('8', '!'+8, state[SDL_SCANCODE_8], key_handled_8);
+ debounce('9', '!'+9, state[SDL_SCANCODE_9], key_handled_9);
+ debounce('0', '!'+0, state[SDL_SCANCODE_0], key_handled_0);
+ }
+ // modify the simulated controller depending on the pressed key
+ void handle_key(SDL_Keycode key) {
+ if (key == 'r') {
+ motorController.StartRinging();
+ } else if (key == 'R') {
+ motorController.StopRinging();
+ } else if (key == 'm') {
+ motorController.RunForDuration(100);
+ } else if (key == 'M') {
+ motorController.RunForDuration(255);
+ } else if (key == 'n') {
+ send_notification();
+ } else if (key == 'N') {
+ notificationManager.ClearNewNotificationFlag();
+ } else if (key == 'b') {
+ bleController.Connect();
+ } else if (key == 'B') {
+ bleController.Disconnect();
+ } else if (key == 'v') {
+ if (batteryController.percentRemaining >= 90) {
+ batteryController.percentRemaining = 100;
+ } else {
+ batteryController.percentRemaining += 10;
+ }
+ } else if (key == 'V') {
+ if (batteryController.percentRemaining <= 10) {
+ batteryController.percentRemaining = 0;
+ } else {
+ batteryController.percentRemaining -= 10;
+ }
+ } else if (key == 'c') {
+ batteryController.isCharging = true;
+ batteryController.isPowerPresent = true;
+ } else if (key == 'C') {
+ batteryController.isCharging = false;
+ batteryController.isPowerPresent = false;
+ } else if (key == 'l') {
+ brightnessController.Higher();
+ } else if (key == 'L') {
+ brightnessController.Lower();
+ } else if (key == 'p') {
+ this->print_memory_usage = true;
+ } else if (key == 'P') {
+ this->print_memory_usage = false;
+ } else if (key == 's') {
+ motionSensor.steps += 500;
+ } else if (key == 'S') {
+ if (motionSensor.steps > 500) {
+ motionSensor.steps -= 500;
+ } else {
+ motionSensor.steps = 0;
+ }
+ } else if (key == 'h') {
+ if (heartRateController.State() == Pinetime::Controllers::HeartRateController::States::Stopped) {
+ heartRateController.Start();
+ } else if (heartRateController.State() == Pinetime::Controllers::HeartRateController::States::NotEnoughData) {
+ heartRateController.Update(Pinetime::Controllers::HeartRateController::States::Running, 10);
+ } else {
+ uint8_t heartrate = heartRateController.HeartRate();
+ heartRateController.Update(Pinetime::Controllers::HeartRateController::States::Running, heartrate + 10);
+ }
+ } else if (key == 'H') {
+ heartRateController.Stop();
+ } else if (key >= '0' && key <= '9') {
+ this->switch_to_screen(key-'0');
+ } else if (key >= '!'+0 && key <= '!'+9) {
+ this->switch_to_screen(key-'!'+10);
+ }
+ batteryController.voltage = batteryController.percentRemaining * 50;
+ }
+
+ void handle_touch_and_button() {
+ int x, y;
+ uint32_t buttons = SDL_GetMouseState(&x, &y);
+ const bool left_click = (buttons & SDL_BUTTON_LMASK) != 0;
+ const bool right_click = (buttons & SDL_BUTTON_RMASK) != 0;
+ if (left_click) {
+ left_release_sent = false;
+ systemTask.OnTouchEvent();
+ return;
+ } else {
+ if (!left_release_sent) {
+ left_release_sent = true;
+ systemTask.OnTouchEvent();
+ return;
+ }
+ }
+ if (right_click != right_last_state) {
+ right_last_state =right_click;
+ systemTask.PushMessage(Pinetime::System::Messages::HandleButtonEvent);
+ return;
+ }
+ }
+
+ // helper function to switch between screens
+ void switch_to_screen(uint8_t screen_idx)
+ {
+ if (screen_idx == 1) {
+ settingsController.SetClockFace(0);
+ displayApp.StartApp(Pinetime::Applications::Apps::Clock, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else if (screen_idx == 2) {
+ settingsController.SetClockFace(1);
+ displayApp.StartApp(Pinetime::Applications::Apps::Clock, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else if (screen_idx == 3) {
+ settingsController.SetClockFace(2);
+ displayApp.StartApp(Pinetime::Applications::Apps::Clock, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else if (screen_idx == 4) {
+ displayApp.StartApp(Pinetime::Applications::Apps::Paddle, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else if (screen_idx == 5) {
+ displayApp.StartApp(Pinetime::Applications::Apps::Twos, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else if (screen_idx == 6) {
+ displayApp.StartApp(Pinetime::Applications::Apps::Metronome, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else if (screen_idx == 7) {
+ displayApp.StartApp(Pinetime::Applications::Apps::FirmwareUpdate, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else if (screen_idx == 8) {
+ displayApp.StartApp(Pinetime::Applications::Apps::BatteryInfo, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else if (screen_idx == 9) {
+ displayApp.StartApp(Pinetime::Applications::Apps::FlashLight, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else if (screen_idx == 0) {
+ displayApp.StartApp(Pinetime::Applications::Apps::QuickSettings, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else if (screen_idx == 11) {
+ displayApp.StartApp(Pinetime::Applications::Apps::Music, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else if (screen_idx == 12) {
+ displayApp.StartApp(Pinetime::Applications::Apps::Paint, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else if (screen_idx == 13) {
+ displayApp.StartApp(Pinetime::Applications::Apps::SysInfo, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else if (screen_idx == 14) {
+ displayApp.StartApp(Pinetime::Applications::Apps::Steps, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else if (screen_idx == 15) {
+ displayApp.StartApp(Pinetime::Applications::Apps::Error, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else if (screen_idx == 17) {
+ displayApp.StartApp(Pinetime::Applications::Apps::PassKey, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else if (screen_idx == 10) {
+ displayApp.StartApp(Pinetime::Applications::Apps::Missing, Pinetime::Applications::DisplayApp::FullRefreshDirections::None);
+ }
+ else {
+ std::cout << "unhandled screen_idx: " << int(screen_idx) << std::endl;
+ }
+ }
+ // render the current status of the simulated controller
+ void refresh_screen() {
+ const Pinetime::Controllers::BrightnessController::Levels level = brightnessController.Level();
+ if (level == Pinetime::Controllers::BrightnessController::Levels::Off) {
+ if (!screen_off_created) {
+ screen_off_created = true;
+ screen_off_bg = lv_obj_create(lv_scr_act(), nullptr);
+ lv_obj_set_size(screen_off_bg, 240, 240);
+ lv_obj_set_pos(screen_off_bg, 0, 0);
+ lv_obj_set_style_local_bg_color(screen_off_bg, LV_OBJ_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_BLACK);
+ screen_off_label = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text_static(screen_off_label, "Screen is OFF");
+ lv_obj_align(screen_off_label, nullptr, LV_ALIGN_CENTER, 0, -20);
+ }
+ } else {
+ if (screen_off_created) {
+ screen_off_created = false;
+ lv_obj_del(screen_off_bg);
+ lv_obj_del(screen_off_label);
+ }
+ }
+ if (print_memory_usage) {
+ // print free memory with the knowledge that 14KiB RAM is the actual PineTime-Memory
+ lv_mem_monitor(&mem_mon);
+ printf("actual free_size = %d\n", int64_t(mem_mon.free_size) - (LV_MEM_SIZE - 14U*1024U));
+ }
+ }
+
+ bool print_memory_usage = false;
+ lv_mem_monitor_t mem_mon;
+
+ // variables to create and destroy an lvgl overlay to indicate a turned off screen
+ bool screen_off_created = false;
+ lv_obj_t *screen_off_bg;
+ lv_obj_t *screen_off_label;
+
+private:
+ bool key_handled_r = false; // r ... enable ringing, R ... disable ringing
+ bool key_handled_m = false; // m ... let motor run, M ... stop motor
+ bool key_handled_n = false; // n ... send notification, N ... clear all notifications
+ bool key_handled_b = false; // b ... connect Bluetooth, B ... disconnect Bluetooth
+ bool key_handled_v = false; // battery Voltage and percentage, v ... increase, V ... decrease
+ bool key_handled_c = false; // c ... charging, C ... not charging
+ bool key_handled_l = false; // l ... increase brightness level, L ... lower brightness level
+ bool key_handled_p = false; // p ... enable print memory usage, P ... disable print memory usage
+ bool key_handled_s = false; // s ... increase step count, S ... decrease step count
+ bool key_handled_h = false; // h ... set heartrate running, H ... stop heartrate
+ // numbers from 0 to 9 to switch between screens
+ bool key_handled_1 = false;
+ bool key_handled_2 = false;
+ bool key_handled_3 = false;
+ bool key_handled_4 = false;
+ bool key_handled_5 = false;
+ bool key_handled_6 = false;
+ bool key_handled_7 = false;
+ bool key_handled_8 = false;
+ bool key_handled_9 = false;
+ bool key_handled_0 = false;
+ bool visible; // show Simulator window
+ int height; // Height of the window
+ int width; // Width of the window
+ SDL_Renderer *renderer = NULL; // Pointer for the renderer
+ SDL_Window *window = NULL; // Pointer for the window
+
+ bool left_release_sent = true; // make sure to send one mouse button release event
+ bool right_last_state = false; // varable used to send message only on changing state
+};
+
+int main(int argc, char **argv)
+{
+ // parse arguments
+ bool fw_status_window_visible = true;
+ bool arg_help = false;
+ for (int i=1; i
+template
+void APP_ERROR_HANDLER(T err) {
+ throw std::runtime_error("APP_ERROR_HANDLER: " + std::to_string(err));
+}
+
+struct SCB_t {
+ unsigned ICSR = 0;
+};
+static SCB_t SCB_member;
+static SCB_t *SCB = &SCB_member;
+
+//#define SCB_ICSR_VECTACTIVE_Msk (0x1FFUL /*<< SCB_ICSR_VECTACTIVE_Pos*/) /*!< SCB ICSR: VECTACTIVE Mask */
+constexpr unsigned SCB_ICSR_VECTACTIVE_Msk = 0x01;
+
+/**
+ \brief System Reset
+ \details Initiates a system reset request to reset the MCU.
+ */
+// copied from nRF5_SDK_15.3.0_59ac345/components/toolchain/cmsis/include/core_cm4.h
+void NVIC_SystemReset(void);
+
+#endif /* INC_FREERTOS_H */
diff --git a/sim/components/battery/BatteryController.cpp b/sim/components/battery/BatteryController.cpp
new file mode 100644
index 0000000..effd0a2
--- /dev/null
+++ b/sim/components/battery/BatteryController.cpp
@@ -0,0 +1,95 @@
+#include "components/battery/BatteryController.h"
+//#include "drivers/PinMap.h"
+//#include
+//#include
+#include
+
+using namespace Pinetime::Controllers;
+
+Battery* Battery::instance = nullptr;
+
+Battery::Battery() {
+ instance = this;
+ //nrf_gpio_cfg_input(PinMap::Charging, static_cast GPIO_PIN_CNF_PULL_Disabled);
+}
+
+void Battery::ReadPowerState() {
+ if (isPowerPresent && !isCharging) {
+ isFull = true;
+ } else if (!isPowerPresent) {
+ isFull = false;
+ }
+}
+
+void Battery::MeasureVoltage() {
+ ReadPowerState();
+
+ if (isReading) {
+ return;
+ }
+ // Non blocking read
+ isReading = true;
+ //SaadcInit();
+
+ //nrfx_saadc_sample();
+}
+
+//void Battery::AdcCallbackStatic(nrfx_saadc_evt_t const* event) {
+// instance->SaadcEventHandler(event);
+//}
+
+//void Battery::SaadcInit() {
+// nrfx_saadc_config_t adcConfig = NRFX_SAADC_DEFAULT_CONFIG;
+// APP_ERROR_CHECK(nrfx_saadc_init(&adcConfig, AdcCallbackStatic));
+//
+// nrf_saadc_channel_config_t adcChannelConfig = {.resistor_p = NRF_SAADC_RESISTOR_DISABLED,
+// .resistor_n = NRF_SAADC_RESISTOR_DISABLED,
+// .gain = NRF_SAADC_GAIN1_4,
+// .reference = NRF_SAADC_REFERENCE_INTERNAL,
+// .acq_time = NRF_SAADC_ACQTIME_40US,
+// .mode = NRF_SAADC_MODE_SINGLE_ENDED,
+// .burst = NRF_SAADC_BURST_ENABLED,
+// .pin_p = batteryVoltageAdcInput,
+// .pin_n = NRF_SAADC_INPUT_DISABLED};
+// APP_ERROR_CHECK(nrfx_saadc_channel_init(0, &adcChannelConfig));
+// APP_ERROR_CHECK(nrfx_saadc_buffer_convert(&saadc_value, 1));
+//}
+//
+//void Battery::SaadcEventHandler(nrfx_saadc_evt_t const* p_event) {
+// const uint16_t battery_max = 4180; // maximum voltage of battery ( max charging voltage is 4.21 )
+// const uint16_t battery_min = 3200; // minimum voltage of battery before shutdown ( depends on the battery )
+//
+// if (p_event->type == NRFX_SAADC_EVT_DONE) {
+//
+// APP_ERROR_CHECK(nrfx_saadc_buffer_convert(&saadc_value, 1));
+//
+// // A hardware voltage divider divides the battery voltage by 2
+// // ADC gain is 1/4
+// // thus adc_voltage = battery_voltage / 2 * gain = battery_voltage / 8
+// // reference_voltage is 600mV
+// // p_event->data.done.p_buffer[0] = (adc_voltage / reference_voltage) * 1024
+// voltage = p_event->data.done.p_buffer[0] * (8 * 600) / 1024;
+//
+// uint8_t newPercent;
+// if (isFull) {
+// newPercent = 100;
+// } else if (voltage < battery_min) {
+// newPercent = 0;
+// } else {
+// newPercent = std::min((voltage - battery_min) * 100 / (battery_max - battery_min), isCharging ? 99 : 100);
+// }
+//
+// if ((isPowerPresent && newPercent > percentRemaining) || (!isPowerPresent && newPercent < percentRemaining) || firstMeasurement) {
+// firstMeasurement = false;
+// percentRemaining = newPercent;
+// systemTask->PushMessage(System::Messages::BatteryPercentageUpdated);
+// }
+//
+// nrfx_saadc_uninit();
+// isReading = false;
+// }
+//}
+
+void Battery::Register(Pinetime::System::SystemTask* systemTask) {
+ this->systemTask = systemTask;
+}
diff --git a/sim/components/battery/BatteryController.h b/sim/components/battery/BatteryController.h
new file mode 100644
index 0000000..d84af45
--- /dev/null
+++ b/sim/components/battery/BatteryController.h
@@ -0,0 +1,57 @@
+#pragma once
+#include
+#include "systemtask/SystemTask.h"
+
+namespace Pinetime {
+ namespace Controllers {
+
+ class Battery {
+ public:
+ Battery();
+
+ void ReadPowerState();
+ void MeasureVoltage();
+ void Register(System::SystemTask* systemTask);
+
+ uint8_t PercentRemaining() const {
+ return percentRemaining;
+ }
+
+ uint16_t Voltage() const {
+ return voltage;
+ }
+
+ bool IsCharging() const {
+ // isCharging will go up and down when fully charged
+ // isFull makes sure this returns false while fully charged.
+ return isCharging && !isFull;
+ }
+
+ bool IsPowerPresent() const {
+ return isPowerPresent;
+ }
+
+ private:
+ static Battery* instance;
+
+ //static constexpr nrf_saadc_input_t batteryVoltageAdcInput = NRF_SAADC_INPUT_AIN7;
+ public:
+ uint16_t voltage = 0;
+ uint8_t percentRemaining = 0;
+
+ bool isFull = false;
+ bool isCharging = false;
+ bool isPowerPresent = false;
+ bool firstMeasurement = true;
+
+ //void SaadcInit();
+
+ //void SaadcEventHandler(nrfx_saadc_evt_t const* p_event);
+ //static void AdcCallbackStatic(nrfx_saadc_evt_t const* event);
+
+ bool isReading = false;
+
+ Pinetime::System::SystemTask* systemTask = nullptr;
+ };
+ }
+}
diff --git a/sim/components/ble/AlertNotificationService.cpp b/sim/components/ble/AlertNotificationService.cpp
new file mode 100644
index 0000000..0ce00b0
--- /dev/null
+++ b/sim/components/ble/AlertNotificationService.cpp
@@ -0,0 +1,125 @@
+#include "components/ble/AlertNotificationService.h"
+#include
+#include
+#include
+#include "components/ble/NotificationManager.h"
+#include "systemtask/SystemTask.h"
+
+using namespace Pinetime::Controllers;
+
+//constexpr ble_uuid16_t AlertNotificationService::ansUuid;
+//constexpr ble_uuid16_t AlertNotificationService::ansCharUuid;
+//constexpr ble_uuid128_t AlertNotificationService::notificationEventUuid;
+
+int AlertNotificationCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
+ auto anService = static_cast(arg);
+ return anService->OnAlert(conn_handle, attr_handle, ctxt);
+}
+
+void AlertNotificationService::Init() {
+// int res;
+// res = ble_gatts_count_cfg(serviceDefinition);
+// ASSERT(res == 0);
+//
+// res = ble_gatts_add_svcs(serviceDefinition);
+// ASSERT(res == 0);
+}
+
+AlertNotificationService::AlertNotificationService(System::SystemTask& systemTask, NotificationManager& notificationManager)
+ :
+// : characteristicDefinition {{.uuid = &ansCharUuid.u, .access_cb = AlertNotificationCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE},
+// {.uuid = ¬ificationEventUuid.u,
+// .access_cb = AlertNotificationCallback,
+// .arg = this,
+// .flags = BLE_GATT_CHR_F_NOTIFY,
+// .val_handle = &eventHandle},
+// {0}},
+// serviceDefinition {
+// {/* Device Information Service */
+// .type = BLE_GATT_SVC_TYPE_PRIMARY,
+// .uuid = &ansUuid.u,
+// .characteristics = characteristicDefinition},
+// {0},
+// },
+ systemTask {systemTask},
+ notificationManager {notificationManager} {
+}
+
+int AlertNotificationService::OnAlert(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt) {
+// if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
+// constexpr size_t stringTerminatorSize = 1; // end of string '\0'
+// constexpr size_t headerSize = 3;
+// const auto maxMessageSize {NotificationManager::MaximumMessageSize()};
+// const auto maxBufferSize {maxMessageSize + headerSize};
+//
+// // Ignore notifications with empty message
+// const auto packetLen = OS_MBUF_PKTLEN(ctxt->om);
+// if (packetLen <= headerSize) {
+// return 0;
+// }
+//
+// size_t bufferSize = std::min(packetLen + stringTerminatorSize, maxBufferSize);
+// auto messageSize = std::min(maxMessageSize, (bufferSize - headerSize));
+// Categories category;
+//
+// NotificationManager::Notification notif;
+// os_mbuf_copydata(ctxt->om, headerSize, messageSize - 1, notif.message.data());
+// os_mbuf_copydata(ctxt->om, 0, 1, &category);
+// notif.message[messageSize - 1] = '\0';
+// notif.size = messageSize;
+//
+// // TODO convert all ANS categories to NotificationController categories
+// switch (category) {
+// case Categories::Call:
+// notif.category = Pinetime::Controllers::NotificationManager::Categories::IncomingCall;
+// break;
+// default:
+// notif.category = Pinetime::Controllers::NotificationManager::Categories::SimpleAlert;
+// break;
+// }
+//
+// auto event = Pinetime::System::Messages::OnNewNotification;
+// notificationManager.Push(std::move(notif));
+// systemTask.PushMessage(event);
+// }
+ return 0;
+}
+
+void AlertNotificationService::AcceptIncomingCall() {
+// auto response = IncomingCallResponses::Answer;
+// auto* om = ble_hs_mbuf_from_flat(&response, 1);
+//
+// uint16_t connectionHandle = systemTask.nimble().connHandle();
+//
+// if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) {
+// return;
+// }
+//
+// ble_gattc_notify_custom(connectionHandle, eventHandle, om);
+}
+
+void AlertNotificationService::RejectIncomingCall() {
+// auto response = IncomingCallResponses::Reject;
+// auto* om = ble_hs_mbuf_from_flat(&response, 1);
+//
+// uint16_t connectionHandle = systemTask.nimble().connHandle();
+//
+// if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) {
+// return;
+// }
+//
+// ble_gattc_notify_custom(connectionHandle, eventHandle, om);
+}
+
+void AlertNotificationService::MuteIncomingCall() {
+// auto response = IncomingCallResponses::Mute;
+// auto* om = ble_hs_mbuf_from_flat(&response, 1);
+//
+// uint16_t connectionHandle = systemTask.nimble().connHandle();
+//
+// if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) {
+// return;
+// }
+//
+// ble_gattc_notify_custom(connectionHandle, eventHandle, om);
+}
diff --git a/sim/components/ble/AlertNotificationService.h b/sim/components/ble/AlertNotificationService.h
new file mode 100644
index 0000000..6105c73
--- /dev/null
+++ b/sim/components/ble/AlertNotificationService.h
@@ -0,0 +1,68 @@
+#pragma once
+#include
+#include
+//#define min // workaround: nimble's min/max macros conflict with libstdc++
+//#define max
+//#include
+//#undef max
+//#undef min
+
+// 00020001-78fc-48fe-8e23-433b3a1942d0
+//#define NOTIFICATION_EVENT_SERVICE_UUID_BASE \
+// { 0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, 0x01, 0x00, 0x02, 0x00 }
+
+namespace Pinetime {
+
+ namespace System {
+ class SystemTask;
+ }
+ namespace Controllers {
+ class NotificationManager;
+
+ class AlertNotificationService {
+ public:
+ AlertNotificationService(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::NotificationManager& notificationManager);
+ void Init();
+
+ int OnAlert(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt);
+
+ void AcceptIncomingCall();
+ void RejectIncomingCall();
+ void MuteIncomingCall();
+
+ enum class IncomingCallResponses : uint8_t { Reject = 0x00, Answer = 0x01, Mute = 0x02 };
+
+ private:
+ enum class Categories : uint8_t {
+ SimpleAlert = 0x00,
+ Email = 0x01,
+ News = 0x02,
+ Call = 0x03,
+ MissedCall = 0x04,
+ MmsSms = 0x05,
+ VoiceMail = 0x06,
+ Schedule = 0x07,
+ HighPrioritizedAlert = 0x08,
+ InstantMessage = 0x09,
+ All = 0xff
+ };
+
+ static constexpr uint16_t ansId {0x1811};
+ static constexpr uint16_t ansCharId {0x2a46};
+
+// static constexpr ble_uuid16_t ansUuid {.u {.type = BLE_UUID_TYPE_16}, .value = ansId};
+
+// static constexpr ble_uuid16_t ansCharUuid {.u {.type = BLE_UUID_TYPE_16}, .value = ansCharId};
+
+// static constexpr ble_uuid128_t notificationEventUuid {.u {.type = BLE_UUID_TYPE_128}, .value = NOTIFICATION_EVENT_SERVICE_UUID_BASE};
+
+// struct ble_gatt_chr_def characteristicDefinition[3];
+// struct ble_gatt_svc_def serviceDefinition[2];
+
+ Pinetime::System::SystemTask& systemTask;
+ NotificationManager& notificationManager;
+
+ uint16_t eventHandle;
+ };
+ }
+}
diff --git a/sim/components/ble/MusicService.cpp b/sim/components/ble/MusicService.cpp
new file mode 100644
index 0000000..5f08847
--- /dev/null
+++ b/sim/components/ble/MusicService.cpp
@@ -0,0 +1,198 @@
+/* Copyright (C) 2020-2021 JF, Adam Pigg, Avamander
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+#include "components/ble/MusicService.h"
+#include "systemtask/SystemTask.h"
+
+namespace {
+ // 0000yyxx-78fc-48fe-8e23-433b3a1942d0
+ //constexpr ble_uuid128_t CharUuid(uint8_t x, uint8_t y) {
+ // return ble_uuid128_t{
+ // .u = {.type = BLE_UUID_TYPE_128},
+ // .value = { 0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, x, y, 0x00, 0x00 }
+ // };
+ //}
+
+ // 00000000-78fc-48fe-8e23-433b3a1942d0
+ //constexpr ble_uuid128_t BaseUuid() {
+ // return CharUuid(0x00, 0x00);
+ //}
+
+ //constexpr ble_uuid128_t msUuid {BaseUuid()};
+
+ //constexpr ble_uuid128_t msEventCharUuid {CharUuid(0x01, 0x00)};
+ //constexpr ble_uuid128_t msStatusCharUuid {CharUuid(0x02, 0x00)};
+ //constexpr ble_uuid128_t msArtistCharUuid {CharUuid(0x03, 0x00)};
+ //constexpr ble_uuid128_t msTrackCharUuid {CharUuid(0x04, 0x00)};
+ //constexpr ble_uuid128_t msAlbumCharUuid {CharUuid(0x05, 0x00)};
+ //constexpr ble_uuid128_t msPositionCharUuid {CharUuid(0x06, 0x00)};
+ //constexpr ble_uuid128_t msTotalLengthCharUuid {CharUuid(0x07, 0x00)};
+ //constexpr ble_uuid128_t msTrackNumberCharUuid {CharUuid(0x08, 0x00)};
+ //constexpr ble_uuid128_t msTrackTotalCharUuid {CharUuid(0x09, 0x00)};
+ //constexpr ble_uuid128_t msPlaybackSpeedCharUuid {CharUuid(0x0a, 0x00)};
+ //constexpr ble_uuid128_t msRepeatCharUuid {CharUuid(0x0b, 0x00)};
+ //constexpr ble_uuid128_t msShuffleCharUuid {CharUuid(0x0c, 0x00)};
+
+ //int MusicCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
+ // return static_cast(arg)->OnCommand(conn_handle, attr_handle, ctxt);
+ //}
+}
+
+Pinetime::Controllers::MusicService::MusicService(Pinetime::System::SystemTask& system) : m_system(system) {
+// characteristicDefinition[0] = {.uuid = &msEventCharUuid.u,
+// .access_cb = MusicCallback,
+// .arg = this,
+// .flags = BLE_GATT_CHR_F_NOTIFY,
+// .val_handle = &eventHandle};
+// characteristicDefinition[1] = {.uuid = &msStatusCharUuid.u,
+// .access_cb = MusicCallback,
+// .arg = this,
+// .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
+// characteristicDefinition[2] = {.uuid = &msTrackCharUuid.u,
+// .access_cb = MusicCallback,
+// .arg = this,
+// .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
+// characteristicDefinition[3] = {.uuid = &msArtistCharUuid.u,
+// .access_cb = MusicCallback,
+// .arg = this,
+// .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
+// characteristicDefinition[4] = {.uuid = &msAlbumCharUuid.u,
+// .access_cb = MusicCallback,
+// .arg = this,
+// .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
+// characteristicDefinition[5] = {.uuid = &msPositionCharUuid.u,
+// .access_cb = MusicCallback,
+// .arg = this,
+// .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
+// characteristicDefinition[6] = {.uuid = &msTotalLengthCharUuid.u,
+// .access_cb = MusicCallback,
+// .arg = this,
+// .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
+// characteristicDefinition[7] = {.uuid = &msTotalLengthCharUuid.u,
+// .access_cb = MusicCallback,
+// .arg = this,
+// .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
+// characteristicDefinition[8] = {.uuid = &msTrackNumberCharUuid.u,
+// .access_cb = MusicCallback,
+// .arg = this,
+// .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
+// characteristicDefinition[9] = {.uuid = &msTrackTotalCharUuid.u,
+// .access_cb = MusicCallback,
+// .arg = this,
+// .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
+// characteristicDefinition[10] = {.uuid = &msPlaybackSpeedCharUuid.u,
+// .access_cb = MusicCallback,
+// .arg = this,
+// .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
+// characteristicDefinition[11] = {.uuid = &msRepeatCharUuid.u,
+// .access_cb = MusicCallback,
+// .arg = this,
+// .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
+// characteristicDefinition[12] = {.uuid = &msShuffleCharUuid.u,
+// .access_cb = MusicCallback,
+// .arg = this,
+// .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
+// characteristicDefinition[13] = {0};
+//
+// serviceDefinition[0] = {
+// .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &msUuid.u, .characteristics = characteristicDefinition};
+// serviceDefinition[1] = {0};
+}
+
+void Pinetime::Controllers::MusicService::Init() {
+ //uint8_t res = 0;
+ //res = ble_gatts_count_cfg(serviceDefinition);
+ //ASSERT(res == 0);
+
+ //res = ble_gatts_add_svcs(serviceDefinition);
+ //ASSERT(res == 0);
+}
+
+//int Pinetime::Controllers::MusicService::OnCommand(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt) {
+// if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
+// size_t notifSize = OS_MBUF_PKTLEN(ctxt->om);
+// char data[notifSize + 1];
+// data[notifSize] = '\0';
+// os_mbuf_copydata(ctxt->om, 0, notifSize, data);
+// char* s = &data[0];
+// if (ble_uuid_cmp(ctxt->chr->uuid, &msArtistCharUuid.u) == 0) {
+// artistName = s;
+// } else if (ble_uuid_cmp(ctxt->chr->uuid, &msTrackCharUuid.u) == 0) {
+// trackName = s;
+// } else if (ble_uuid_cmp(ctxt->chr->uuid, &msAlbumCharUuid.u) == 0) {
+// albumName = s;
+// } else if (ble_uuid_cmp(ctxt->chr->uuid, &msStatusCharUuid.u) == 0) {
+// playing = s[0];
+// } else if (ble_uuid_cmp(ctxt->chr->uuid, &msRepeatCharUuid.u) == 0) {
+// repeat = s[0];
+// } else if (ble_uuid_cmp(ctxt->chr->uuid, &msShuffleCharUuid.u) == 0) {
+// shuffle = s[0];
+// } else if (ble_uuid_cmp(ctxt->chr->uuid, &msPositionCharUuid.u) == 0) {
+// trackProgress = (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3];
+// } else if (ble_uuid_cmp(ctxt->chr->uuid, &msTotalLengthCharUuid.u) == 0) {
+// trackLength = (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3];
+// } else if (ble_uuid_cmp(ctxt->chr->uuid, &msTrackNumberCharUuid.u) == 0) {
+// trackNumber = (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3];
+// } else if (ble_uuid_cmp(ctxt->chr->uuid, &msTrackTotalCharUuid.u) == 0) {
+// tracksTotal = (s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3];
+// } else if (ble_uuid_cmp(ctxt->chr->uuid, &msPlaybackSpeedCharUuid.u) == 0) {
+// playbackSpeed = static_cast(((s[0] << 24) | (s[1] << 16) | (s[2] << 8) | s[3])) / 100.0f;
+// }
+// }
+// return 0;
+//}
+
+std::string Pinetime::Controllers::MusicService::getAlbum() const {
+ return albumName;
+}
+
+std::string Pinetime::Controllers::MusicService::getArtist() const {
+ return artistName;
+}
+
+std::string Pinetime::Controllers::MusicService::getTrack() const {
+ return trackName;
+}
+
+bool Pinetime::Controllers::MusicService::isPlaying() const {
+ return playing;
+}
+
+float Pinetime::Controllers::MusicService::getPlaybackSpeed() const {
+ return playbackSpeed;
+}
+
+int Pinetime::Controllers::MusicService::getProgress() const {
+ return trackProgress;
+}
+
+int Pinetime::Controllers::MusicService::getTrackLength() const {
+ return trackLength;
+}
+
+void Pinetime::Controllers::MusicService::event(char event) {
+ std::ignore = event;
+ //auto* om = ble_hs_mbuf_from_flat(&event, 1);
+
+ //uint16_t connectionHandle = m_system.nimble().connHandle();
+
+ //if (connectionHandle == 0 || connectionHandle == BLE_HS_CONN_HANDLE_NONE) {
+ // return;
+ //}
+
+ //ble_gattc_notify_custom(connectionHandle, eventHandle, om);
+}
diff --git a/sim/components/ble/MusicService.h b/sim/components/ble/MusicService.h
new file mode 100644
index 0000000..554c343
--- /dev/null
+++ b/sim/components/ble/MusicService.h
@@ -0,0 +1,87 @@
+/* Copyright (C) 2020-2021 JF, Adam Pigg, Avamander
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+#pragma once
+
+#include
+#include
+
+namespace Pinetime {
+ namespace System {
+ class SystemTask;
+ }
+ namespace Controllers {
+ class MusicService {
+ public:
+ explicit MusicService(Pinetime::System::SystemTask& system);
+
+ void Init();
+
+ //int OnCommand(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt);
+
+ void event(char event);
+
+ std::string getArtist() const;
+
+ std::string getTrack() const;
+
+ std::string getAlbum() const;
+
+ int getProgress() const;
+
+ int getTrackLength() const;
+
+ float getPlaybackSpeed() const;
+
+ bool isPlaying() const;
+
+ static const char EVENT_MUSIC_OPEN = 0xe0;
+ static const char EVENT_MUSIC_PLAY = 0x00;
+ static const char EVENT_MUSIC_PAUSE = 0x01;
+ static const char EVENT_MUSIC_NEXT = 0x03;
+ static const char EVENT_MUSIC_PREV = 0x04;
+ static const char EVENT_MUSIC_VOLUP = 0x05;
+ static const char EVENT_MUSIC_VOLDOWN = 0x06;
+
+ enum MusicStatus { NotPlaying = 0x00, Playing = 0x01 };
+
+ private:
+ //struct ble_gatt_chr_def characteristicDefinition[14];
+ //struct ble_gatt_svc_def serviceDefinition[2];
+
+ uint16_t eventHandle {};
+
+ std::string artistName {"Waiting for"};
+ std::string albumName {};
+ std::string trackName {"track information.."};
+
+ bool playing {false};
+
+ int trackProgress {0};
+ int trackLength {0};
+ int trackNumber {};
+ int tracksTotal {};
+
+ float playbackSpeed {1.0f};
+
+ bool repeat {false};
+ bool shuffle {false};
+
+ Pinetime::System::SystemTask& m_system;
+ };
+ }
+}
diff --git a/sim/components/ble/NavigationService.cpp b/sim/components/ble/NavigationService.cpp
new file mode 100644
index 0000000..38a3d85
--- /dev/null
+++ b/sim/components/ble/NavigationService.cpp
@@ -0,0 +1,111 @@
+/* Copyright (C) 2021 Adam Pigg
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+
+#include "components/ble/NavigationService.h"
+
+#include "systemtask/SystemTask.h"
+
+namespace {
+// // 0001yyxx-78fc-48fe-8e23-433b3a1942d0
+// constexpr ble_uuid128_t CharUuid(uint8_t x, uint8_t y) {
+// return ble_uuid128_t {.u = {.type = BLE_UUID_TYPE_128},
+// .value = {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, x, y, 0x01, 0x00}};
+// }
+//
+// // 00010000-78fc-48fe-8e23-433b3a1942d0
+// constexpr ble_uuid128_t BaseUuid() {
+// return CharUuid(0x00, 0x00);
+// }
+//
+// constexpr ble_uuid128_t navUuid {BaseUuid()};
+//
+// constexpr ble_uuid128_t navFlagCharUuid {CharUuid(0x01, 0x00)};
+// constexpr ble_uuid128_t navNarrativeCharUuid {CharUuid(0x02, 0x00)};
+// constexpr ble_uuid128_t navManDistCharUuid {CharUuid(0x03, 0x00)};
+// constexpr ble_uuid128_t navProgressCharUuid {CharUuid(0x04, 0x00)};
+//
+// int NAVCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
+// auto navService = static_cast(arg);
+// return navService->OnCommand(conn_handle, attr_handle, ctxt);
+// }
+} // namespace
+
+Pinetime::Controllers::NavigationService::NavigationService(Pinetime::System::SystemTask& system) : m_system(system) {
+// characteristicDefinition[0] = {
+// .uuid = &navFlagCharUuid.u, .access_cb = NAVCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
+//
+// characteristicDefinition[1] = {
+// .uuid = &navNarrativeCharUuid.u, .access_cb = NAVCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
+// characteristicDefinition[2] = {
+// .uuid = &navManDistCharUuid.u, .access_cb = NAVCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
+// characteristicDefinition[3] = {
+// .uuid = &navProgressCharUuid.u, .access_cb = NAVCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ};
+//
+// characteristicDefinition[4] = {0};
+//
+// serviceDefinition[0] = {.type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &navUuid.u, .characteristics = characteristicDefinition};
+// serviceDefinition[1] = {0};
+
+ m_progress = 0;
+}
+
+void Pinetime::Controllers::NavigationService::Init() {
+// int res = 0;
+// res = ble_gatts_count_cfg(serviceDefinition);
+// ASSERT(res == 0);
+//
+// res = ble_gatts_add_svcs(serviceDefinition);
+// ASSERT(res == 0);
+}
+
+//int Pinetime::Controllers::NavigationService::OnCommand(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt) {
+//
+// if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
+// size_t notifSize = OS_MBUF_PKTLEN(ctxt->om);
+// uint8_t data[notifSize + 1];
+// data[notifSize] = '\0';
+// os_mbuf_copydata(ctxt->om, 0, notifSize, data);
+// char* s = (char*) &data[0];
+// if (ble_uuid_cmp(ctxt->chr->uuid, &navFlagCharUuid.u) == 0) {
+// m_flag = s;
+// } else if (ble_uuid_cmp(ctxt->chr->uuid, &navNarrativeCharUuid.u) == 0) {
+// m_narrative = s;
+// } else if (ble_uuid_cmp(ctxt->chr->uuid, &navManDistCharUuid.u) == 0) {
+// m_manDist = s;
+// } else if (ble_uuid_cmp(ctxt->chr->uuid, &navProgressCharUuid.u) == 0) {
+// m_progress = data[0];
+// }
+// }
+// return 0;
+//}
+
+std::string Pinetime::Controllers::NavigationService::getFlag() {
+ return m_flag;
+}
+
+std::string Pinetime::Controllers::NavigationService::getNarrative() {
+ return m_narrative;
+}
+
+std::string Pinetime::Controllers::NavigationService::getManDist() {
+ return m_manDist;
+}
+
+int Pinetime::Controllers::NavigationService::getProgress() {
+ return m_progress;
+}
diff --git a/sim/components/ble/NavigationService.h b/sim/components/ble/NavigationService.h
new file mode 100644
index 0000000..afa89eb
--- /dev/null
+++ b/sim/components/ble/NavigationService.h
@@ -0,0 +1,63 @@
+/* Copyright (C) 2021 Adam Pigg
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+#pragma once
+
+#include
+#include
+//#define min // workaround: nimble's min/max macros conflict with libstdc++
+//#define max
+//#include
+//#include
+//#undef max
+//#undef min
+
+namespace Pinetime {
+ namespace System {
+ class SystemTask;
+ }
+ namespace Controllers {
+
+ class NavigationService {
+ public:
+ explicit NavigationService(Pinetime::System::SystemTask& system);
+
+ void Init();
+
+// int OnCommand(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt);
+
+ std::string getFlag();
+
+ std::string getNarrative();
+
+ std::string getManDist();
+
+ int getProgress();
+
+ private:
+// struct ble_gatt_chr_def characteristicDefinition[5];
+// struct ble_gatt_svc_def serviceDefinition[2];
+
+ std::string m_flag;
+ std::string m_narrative;
+ std::string m_manDist;
+ int m_progress = 0;
+
+ Pinetime::System::SystemTask& m_system;
+ };
+ }
+}
diff --git a/sim/components/ble/NimbleController.cpp b/sim/components/ble/NimbleController.cpp
new file mode 100644
index 0000000..b0c87ee
--- /dev/null
+++ b/sim/components/ble/NimbleController.cpp
@@ -0,0 +1,456 @@
+#include "components/ble/NimbleController.h"
+#include
+
+//#include
+//#define min // workaround: nimble's min/max macros conflict with libstdc++
+//#define max
+//#include
+//#include
+//#include
+//#include
+//#include
+//#include
+//#include
+//#include
+//#undef max
+//#undef min
+#include "components/ble/BleController.h"
+#include "components/ble/NotificationManager.h"
+#include "components/datetime/DateTimeController.h"
+#include "components/fs/FS.h"
+#include "systemtask/SystemTask.h"
+
+using namespace Pinetime::Controllers;
+
+NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
+ Pinetime::Controllers::Ble& bleController,
+ DateTime& dateTimeController,
+ Pinetime::Controllers::NotificationManager& notificationManager,
+ Controllers::Battery& batteryController,
+ Pinetime::Drivers::SpiNorFlash& spiNorFlash,
+ Controllers::HeartRateController& heartRateController,
+ Controllers::MotionController& motionController,
+ Controllers::FS& fs)
+ : systemTask {systemTask},
+ bleController {bleController},
+ dateTimeController {dateTimeController},
+ notificationManager {notificationManager},
+ spiNorFlash {spiNorFlash},
+ fs {fs},
+// dfuService {systemTask, bleController, spiNorFlash},
+
+// currentTimeClient {dateTimeController},
+ anService {systemTask, notificationManager},
+// alertNotificationClient {systemTask, notificationManager},
+// currentTimeService {dateTimeController},
+ musicService {systemTask},
+ weatherService {systemTask, dateTimeController},
+ navService {systemTask} {
+// batteryInformationService {batteryController},
+// immediateAlertService {systemTask, notificationManager},
+// heartRateService {systemTask, heartRateController},
+// motionService {systemTask, motionController},
+// fsService {systemTask, fs},
+// serviceDiscovery({¤tTimeClient, &alertNotificationClient}) {
+}
+
+//void nimble_on_reset(int reason) {
+// NRF_LOG_INFO("Nimble lost sync, resetting state; reason=%d", reason);
+//}
+//
+//void nimble_on_sync(void) {
+// int rc;
+//
+// NRF_LOG_INFO("Nimble is synced");
+//
+// rc = ble_hs_util_ensure_addr(0);
+// ASSERT(rc == 0);
+//
+// nptr->StartAdvertising();
+//}
+//
+//int GAPEventCallback(struct ble_gap_event* event, void* arg) {
+// auto nimbleController = static_cast(arg);
+// return nimbleController->OnGAPEvent(event);
+//}
+
+void NimbleController::Init() {
+// while (!ble_hs_synced()) {
+// }
+//
+// nptr = this;
+// ble_hs_cfg.reset_cb = nimble_on_reset;
+// ble_hs_cfg.sync_cb = nimble_on_sync;
+// ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
+//
+// ble_svc_gap_init();
+// ble_svc_gatt_init();
+//
+// deviceInformationService.Init();
+// currentTimeClient.Init();
+// currentTimeService.Init();
+ musicService.Init();
+ weatherService.Init();
+ navService.Init();
+// anService.Init();
+// dfuService.Init();
+// batteryInformationService.Init();
+// immediateAlertService.Init();
+// heartRateService.Init();
+// motionService.Init();
+// fsService.Init();
+//
+// int rc;
+// rc = ble_hs_util_ensure_addr(0);
+// ASSERT(rc == 0);
+// rc = ble_hs_id_infer_auto(0, &addrType);
+// ASSERT(rc == 0);
+// rc = ble_svc_gap_device_name_set(deviceName);
+// ASSERT(rc == 0);
+// rc = ble_svc_gap_device_appearance_set(0xC2);
+// ASSERT(rc == 0);
+// Pinetime::Controllers::Ble::BleAddress address;
+// rc = ble_hs_id_copy_addr(addrType, address.data(), nullptr);
+// ASSERT(rc == 0);
+//
+// bleController.Address(std::move(address));
+// switch (addrType) {
+// case BLE_OWN_ADDR_PUBLIC:
+// bleController.AddressType(Ble::AddressTypes::Public);
+// break;
+// case BLE_OWN_ADDR_RANDOM:
+// bleController.AddressType(Ble::AddressTypes::Random);
+// break;
+// case BLE_OWN_ADDR_RPA_PUBLIC_DEFAULT:
+// bleController.AddressType(Ble::AddressTypes::RPA_Public);
+// break;
+// case BLE_OWN_ADDR_RPA_RANDOM_DEFAULT:
+// bleController.AddressType(Ble::AddressTypes::RPA_Random);
+// break;
+// }
+//
+// rc = ble_gatts_start();
+// ASSERT(rc == 0);
+//
+// RestoreBond();
+//
+// StartAdvertising();
+}
+
+//void NimbleController::StartAdvertising() {
+// struct ble_gap_adv_params adv_params;
+// struct ble_hs_adv_fields fields;
+// struct ble_hs_adv_fields rsp_fields;
+//
+// memset(&adv_params, 0, sizeof(adv_params));
+// memset(&fields, 0, sizeof(fields));
+// memset(&rsp_fields, 0, sizeof(rsp_fields));
+//
+// adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
+// adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
+// /* fast advertise for 30 sec */
+// if (fastAdvCount < 15) {
+// adv_params.itvl_min = 32;
+// adv_params.itvl_max = 47;
+// fastAdvCount++;
+// } else {
+// adv_params.itvl_min = 1636;
+// adv_params.itvl_max = 1651;
+// }
+//
+// fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
+// fields.uuids128 = &dfuServiceUuid;
+// fields.num_uuids128 = 1;
+// fields.uuids128_is_complete = 1;
+// fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
+//
+// rsp_fields.name = reinterpret_cast(deviceName);
+// rsp_fields.name_len = strlen(deviceName);
+// rsp_fields.name_is_complete = 1;
+//
+// int rc;
+// rc = ble_gap_adv_set_fields(&fields);
+// ASSERT(rc == 0);
+//
+// rc = ble_gap_adv_rsp_set_fields(&rsp_fields);
+// ASSERT(rc == 0);
+//
+// rc = ble_gap_adv_start(addrType, NULL, 2000, &adv_params, GAPEventCallback, this);
+// ASSERT(rc == 0);
+//}
+//
+//int NimbleController::OnGAPEvent(ble_gap_event* event) {
+// switch (event->type) {
+// case BLE_GAP_EVENT_ADV_COMPLETE:
+// NRF_LOG_INFO("Advertising event : BLE_GAP_EVENT_ADV_COMPLETE");
+// NRF_LOG_INFO("reason=%d; status=%0X", event->adv_complete.reason, event->connect.status);
+// StartAdvertising();
+// break;
+//
+// case BLE_GAP_EVENT_CONNECT:
+// /* A new connection was established or a connection attempt failed. */
+// NRF_LOG_INFO("Connect event : BLE_GAP_EVENT_CONNECT");
+// NRF_LOG_INFO("connection %s; status=%0X ", event->connect.status == 0 ? "established" : "failed", event->connect.status);
+//
+// if (event->connect.status != 0) {
+// /* Connection failed; resume advertising. */
+// currentTimeClient.Reset();
+// alertNotificationClient.Reset();
+// connectionHandle = BLE_HS_CONN_HANDLE_NONE;
+// bleController.Disconnect();
+// fastAdvCount = 0;
+// StartAdvertising();
+// } else {
+// connectionHandle = event->connect.conn_handle;
+// bleController.Connect();
+// systemTask.PushMessage(Pinetime::System::Messages::BleConnected);
+// // Service discovery is deferred via systemtask
+// }
+// break;
+//
+// case BLE_GAP_EVENT_DISCONNECT:
+// /* Connection terminated; resume advertising. */
+// NRF_LOG_INFO("Disconnect event : BLE_GAP_EVENT_DISCONNECT");
+// NRF_LOG_INFO("disconnect reason=%d", event->disconnect.reason);
+//
+// if (event->disconnect.conn.sec_state.bonded) {
+// PersistBond(event->disconnect.conn);
+// }
+//
+// currentTimeClient.Reset();
+// alertNotificationClient.Reset();
+// connectionHandle = BLE_HS_CONN_HANDLE_NONE;
+// bleController.Disconnect();
+// fastAdvCount = 0;
+// StartAdvertising();
+// break;
+//
+// case BLE_GAP_EVENT_CONN_UPDATE:
+// /* The central has updated the connection parameters. */
+// NRF_LOG_INFO("Update event : BLE_GAP_EVENT_CONN_UPDATE");
+// NRF_LOG_INFO("update status=%0X ", event->conn_update.status);
+// break;
+//
+// case BLE_GAP_EVENT_CONN_UPDATE_REQ:
+// /* The central has requested updated connection parameters */
+// NRF_LOG_INFO("Update event : BLE_GAP_EVENT_CONN_UPDATE_REQ");
+// NRF_LOG_INFO("update request : itvl_min=%d itvl_max=%d latency=%d supervision=%d",
+// event->conn_update_req.peer_params->itvl_min,
+// event->conn_update_req.peer_params->itvl_max,
+// event->conn_update_req.peer_params->latency,
+// event->conn_update_req.peer_params->supervision_timeout);
+// break;
+//
+// case BLE_GAP_EVENT_ENC_CHANGE:
+// /* Encryption has been enabled or disabled for this connection. */
+// NRF_LOG_INFO("Security event : BLE_GAP_EVENT_ENC_CHANGE");
+// NRF_LOG_INFO("encryption change event; status=%0X ", event->enc_change.status);
+//
+// if (event->enc_change.status == 0) {
+// struct ble_gap_conn_desc desc;
+// ble_gap_conn_find(event->enc_change.conn_handle, &desc);
+// if (desc.sec_state.bonded) {
+// PersistBond(desc);
+// }
+//
+// NRF_LOG_INFO("new state: encrypted=%d authenticated=%d bonded=%d key_size=%d",
+// desc.sec_state.encrypted,
+// desc.sec_state.authenticated,
+// desc.sec_state.bonded,
+// desc.sec_state.key_size);
+// }
+// break;
+//
+// case BLE_GAP_EVENT_PASSKEY_ACTION:
+// /* Authentication has been requested for this connection.
+// *
+// * BLE authentication is determined by the combination of I/O capabilities
+// * on the central and peripheral. When the peripheral is display only and
+// * the central has a keyboard and display then passkey auth is selected.
+// * When both the central and peripheral have displays and support yes/no
+// * buttons then numeric comparison is selected. We currently advertise
+// * display capability only so we only handle the "display" action here.
+// *
+// * Standards insist that the rand() PRNG be deterministic.
+// * Use the tinycrypt prng here since rand() is predictable.
+// */
+// NRF_LOG_INFO("Security event : BLE_GAP_EVENT_PASSKEY_ACTION");
+// if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
+// struct ble_sm_io pkey = {0};
+// pkey.action = event->passkey.params.action;
+// pkey.passkey = ble_ll_rand() % 1000000;
+// bleController.SetPairingKey(pkey.passkey);
+// systemTask.PushMessage(Pinetime::System::Messages::OnPairing);
+// ble_sm_inject_io(event->passkey.conn_handle, &pkey);
+// }
+// break;
+//
+// case BLE_GAP_EVENT_SUBSCRIBE:
+// NRF_LOG_INFO("Subscribe event; conn_handle=%d attr_handle=%d "
+// "reason=%d prevn=%d curn=%d previ=%d curi=???\n",
+// event->subscribe.conn_handle,
+// event->subscribe.attr_handle,
+// event->subscribe.reason,
+// event->subscribe.prev_notify,
+// event->subscribe.cur_notify,
+// event->subscribe.prev_indicate);
+//
+// if (event->subscribe.reason == BLE_GAP_SUBSCRIBE_REASON_TERM) {
+// heartRateService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
+// motionService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
+// } else if (event->subscribe.prev_notify == 0 && event->subscribe.cur_notify == 1) {
+// heartRateService.SubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
+// motionService.SubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
+// } else if (event->subscribe.prev_notify == 1 && event->subscribe.cur_notify == 0) {
+// heartRateService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
+// motionService.UnsubscribeNotification(event->subscribe.conn_handle, event->subscribe.attr_handle);
+// }
+// break;
+//
+// case BLE_GAP_EVENT_MTU:
+// NRF_LOG_INFO("MTU Update event; conn_handle=%d cid=%d mtu=%d", event->mtu.conn_handle, event->mtu.channel_id, event->mtu.value);
+// break;
+//
+// case BLE_GAP_EVENT_REPEAT_PAIRING: {
+// NRF_LOG_INFO("Pairing event : BLE_GAP_EVENT_REPEAT_PAIRING");
+// /* We already have a bond with the peer, but it is attempting to
+// * establish a new secure link. This app sacrifices security for
+// * convenience: just throw away the old bond and accept the new link.
+// */
+//
+// /* Delete the old bond. */
+// struct ble_gap_conn_desc desc;
+// ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
+// ble_store_util_delete_peer(&desc.peer_id_addr);
+//
+// /* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
+// * continue with the pairing operation.
+// */
+// }
+// return BLE_GAP_REPEAT_PAIRING_RETRY;
+//
+// case BLE_GAP_EVENT_NOTIFY_RX: {
+// /* Peer sent us a notification or indication. */
+// /* Attribute data is contained in event->notify_rx.attr_data. */
+// NRF_LOG_INFO("Notify event : BLE_GAP_EVENT_NOTIFY_RX");
+// size_t notifSize = OS_MBUF_PKTLEN(event->notify_rx.om);
+//
+// NRF_LOG_INFO("received %s; conn_handle=%d attr_handle=%d "
+// "attr_len=%d",
+// event->notify_rx.indication ? "indication" : "notification",
+// event->notify_rx.conn_handle,
+// event->notify_rx.attr_handle,
+// notifSize);
+//
+// alertNotificationClient.OnNotification(event);
+// } break;
+//
+// case BLE_GAP_EVENT_NOTIFY_TX:
+// NRF_LOG_INFO("Notify event : BLE_GAP_EVENT_NOTIFY_TX");
+// break;
+//
+// case BLE_GAP_EVENT_IDENTITY_RESOLVED:
+// NRF_LOG_INFO("Identity event : BLE_GAP_EVENT_IDENTITY_RESOLVED");
+// break;
+//
+// default:
+// NRF_LOG_INFO("UNHANDLED GAP event : %d", event->type);
+// break;
+// }
+// return 0;
+//}
+
+void NimbleController::StartDiscovery() {
+// if (connectionHandle != BLE_HS_CONN_HANDLE_NONE) {
+// serviceDiscovery.StartDiscovery(connectionHandle);
+// }
+}
+
+//uint16_t NimbleController::connHandle() {
+// return connectionHandle;
+//}
+
+void NimbleController::NotifyBatteryLevel(uint8_t level) {
+// if (connectionHandle != BLE_HS_CONN_HANDLE_NONE) {
+// batteryInformationService.NotifyBatteryLevel(connectionHandle, level);
+// }
+}
+
+//void NimbleController::PersistBond(struct ble_gap_conn_desc& desc) {
+// union ble_store_key key;
+// union ble_store_value our_sec, peer_sec, peer_cccd_set[MYNEWT_VAL(BLE_STORE_MAX_CCCDS)] = {0};
+// int rc;
+//
+// memset(&key, 0, sizeof key);
+// memset(&our_sec, 0, sizeof our_sec);
+// key.sec.peer_addr = desc.peer_id_addr;
+// rc = ble_store_read_our_sec(&key.sec, &our_sec.sec);
+//
+// if (memcmp(&our_sec.sec, &bondId, sizeof bondId) == 0) {
+// return;
+// }
+//
+// memcpy(&bondId, &our_sec.sec, sizeof bondId);
+//
+// memset(&key, 0, sizeof key);
+// memset(&peer_sec, 0, sizeof peer_sec);
+// key.sec.peer_addr = desc.peer_id_addr;
+// rc += ble_store_read_peer_sec(&key.sec, &peer_sec.sec);
+//
+// if (rc == 0) {
+// memset(&key, 0, sizeof key);
+// key.cccd.peer_addr = desc.peer_id_addr;
+// int peer_count = 0;
+// ble_store_util_count(BLE_STORE_OBJ_TYPE_CCCD, &peer_count);
+// for (int i = 0; i < peer_count; i++) {
+// key.cccd.idx = peer_count;
+// ble_store_read_cccd(&key.cccd, &peer_cccd_set[i].cccd);
+// }
+//
+// /* Wakeup Spi and SpiNorFlash before accessing the file system
+// * This should be fixed in the FS driver
+// */
+// systemTask.PushMessage(Pinetime::System::Messages::GoToRunning);
+// systemTask.PushMessage(Pinetime::System::Messages::DisableSleeping);
+// vTaskDelay(10);
+//
+// lfs_file_t file_p;
+//
+// rc = fs.FileOpen(&file_p, "/bond.dat", LFS_O_WRONLY | LFS_O_CREAT);
+// if (rc == 0) {
+// fs.FileWrite(&file_p, reinterpret_cast(&our_sec.sec), sizeof our_sec);
+// fs.FileWrite(&file_p, reinterpret_cast(&peer_sec.sec), sizeof peer_sec);
+// fs.FileWrite(&file_p, reinterpret_cast(&peer_count), 1);
+// for (int i = 0; i < peer_count; i++) {
+// fs.FileWrite(&file_p, reinterpret_cast(&peer_cccd_set[i].cccd), sizeof(struct ble_store_value_cccd));
+// }
+// fs.FileClose(&file_p);
+// }
+// systemTask.PushMessage(Pinetime::System::Messages::EnableSleeping);
+// }
+//}
+
+//void NimbleController::RestoreBond() {
+// lfs_file_t file_p;
+// union ble_store_value sec, cccd;
+// uint8_t peer_count = 0;
+//
+// if (fs.FileOpen(&file_p, "/bond.dat", LFS_O_RDONLY) == 0) {
+// memset(&sec, 0, sizeof sec);
+// fs.FileRead(&file_p, reinterpret_cast(&sec.sec), sizeof sec);
+// ble_store_write_our_sec(&sec.sec);
+//
+// memset(&sec, 0, sizeof sec);
+// fs.FileRead(&file_p, reinterpret_cast(&sec.sec), sizeof sec);
+// ble_store_write_peer_sec(&sec.sec);
+//
+// fs.FileRead(&file_p, &peer_count, 1);
+// for (int i = 0; i < peer_count; i++) {
+// fs.FileRead(&file_p, reinterpret_cast(&cccd.cccd), sizeof(struct ble_store_value_cccd));
+// ble_store_write_cccd(&cccd.cccd);
+// }
+//
+// fs.FileClose(&file_p);
+// fs.FileDelete("/bond.dat");
+// }
+//}
diff --git a/sim/components/ble/NimbleController.h b/sim/components/ble/NimbleController.h
new file mode 100644
index 0000000..0699945
--- /dev/null
+++ b/sim/components/ble/NimbleController.h
@@ -0,0 +1,132 @@
+#pragma once
+
+#include
+
+//#define min // workaround: nimble's min/max macros conflict with libstdc++
+//#define max
+//#include
+//#undef max
+//#undef min
+//#include "components/ble/AlertNotificationClient.h"
+#include "components/ble/AlertNotificationService.h"
+//#include "components/ble/BatteryInformationService.h"
+//#include "components/ble/CurrentTimeClient.h"
+//#include "components/ble/CurrentTimeService.h"
+//#include "components/ble/DeviceInformationService.h"
+//#include "components/ble/DfuService.h"
+//#include "components/ble/HeartRateService.h"
+//#include "components/ble/ImmediateAlertService.h"
+#include "components/ble/MusicService.h"
+#include "components/ble/NavigationService.h"
+//#include "components/ble/ServiceDiscovery.h"
+//#include "components/ble/MotionService.h"
+#include "components/ble/weather/WeatherService.h"
+#include "components/fs/FS.h"
+//#include "components/ble/FSService.h"
+
+namespace Pinetime {
+ namespace Drivers {
+ class SpiNorFlash;
+ }
+
+ namespace System {
+ class SystemTask;
+ }
+
+ namespace Controllers {
+ class Battery;
+ class Ble;
+ class DateTime;
+ class FS;
+ class HeartRateController;
+ class MotionController;
+ class NotificationManager;
+
+ class NimbleController {
+
+ public:
+ NimbleController(Pinetime::System::SystemTask& systemTask,
+ Pinetime::Controllers::Ble& bleController,
+ DateTime& dateTimeController,
+ Pinetime::Controllers::NotificationManager& notificationManager,
+ Controllers::Battery& batteryController,
+ Pinetime::Drivers::SpiNorFlash& spiNorFlash,
+ Controllers::HeartRateController& heartRateController,
+ Controllers::MotionController& motionController,
+ Pinetime::Controllers::FS& fs);
+ void Init();
+ void StartAdvertising();
+// int OnGAPEvent(ble_gap_event* event);
+
+// int OnDiscoveryEvent(uint16_t i, const ble_gatt_error* pError, const ble_gatt_svc* pSvc);
+// int OnCTSCharacteristicDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error* error, const ble_gatt_chr* characteristic);
+// int OnANSCharacteristicDiscoveryEvent(uint16_t connectionHandle, const ble_gatt_error* error, const ble_gatt_chr* characteristic);
+// int OnCurrentTimeReadResult(uint16_t connectionHandle, const ble_gatt_error* error, ble_gatt_attr* attribute);
+// int OnANSDescriptorDiscoveryEventCallback(uint16_t connectionHandle,
+// const ble_gatt_error* error,
+// uint16_t characteristicValueHandle,
+// const ble_gatt_dsc* descriptor);
+
+ void StartDiscovery();
+
+ Pinetime::Controllers::MusicService& music() {
+ return musicService;
+ };
+ Pinetime::Controllers::NavigationService& navigation() {
+ return navService;
+ };
+ Pinetime::Controllers::AlertNotificationService& alertService() {
+ return anService;
+ };
+ Pinetime::Controllers::WeatherService& weather() {
+ return weatherService;
+ };
+
+ uint16_t connHandle();
+ void NotifyBatteryLevel(uint8_t level);
+
+ void RestartFastAdv() {
+ fastAdvCount = 0;
+ }
+
+ private:
+// void PersistBond(struct ble_gap_conn_desc& desc);
+// void RestoreBond();
+
+ static constexpr const char* deviceName = "InfiniTime";
+ Pinetime::System::SystemTask& systemTask;
+ Pinetime::Controllers::Ble& bleController;
+ DateTime& dateTimeController;
+ Pinetime::Controllers::NotificationManager& notificationManager;
+ Pinetime::Drivers::SpiNorFlash& spiNorFlash;
+ Pinetime::Controllers::FS& fs;
+// Pinetime::Controllers::DfuService dfuService;
+
+// DeviceInformationService deviceInformationService;
+// CurrentTimeClient currentTimeClient;
+ AlertNotificationService anService;
+// AlertNotificationClient alertNotificationClient;
+// CurrentTimeService currentTimeService;
+ MusicService musicService;
+ WeatherService weatherService;
+ NavigationService navService;
+// BatteryInformationService batteryInformationService;
+// ImmediateAlertService immediateAlertService;
+// HeartRateService heartRateService;
+// MotionService motionService;
+// FSService fsService;
+// ServiceDiscovery serviceDiscovery;
+
+ uint8_t addrType;
+// uint16_t connectionHandle = BLE_HS_CONN_HANDLE_NONE;
+ uint8_t fastAdvCount = 0;
+ uint8_t bondId[16] = {0};
+
+// ble_uuid128_t dfuServiceUuid {
+// .u {.type = BLE_UUID_TYPE_128},
+// .value = {0x23, 0xD1, 0xBC, 0xEA, 0x5F, 0x78, 0x23, 0x15, 0xDE, 0xEF, 0x12, 0x12, 0x30, 0x15, 0x00, 0x00}};
+ };
+
+// static NimbleController* nptr;
+ }
+}
diff --git a/sim/components/ble/weather/WeatherService.cpp b/sim/components/ble/weather/WeatherService.cpp
new file mode 100644
index 0000000..5086f64
--- /dev/null
+++ b/sim/components/ble/weather/WeatherService.cpp
@@ -0,0 +1,602 @@
+/* Copyright (C) 2021 Avamander
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+//#include
+#include "components/ble/weather/WeatherService.h"
+//#include "libs/QCBOR/inc/qcbor/qcbor.h"
+#include "systemtask/SystemTask.h"
+
+using namespace Pinetime::Controllers;
+
+int WeatherCallback(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
+ return static_cast(arg)->OnCommand(connHandle, attrHandle, ctxt);
+}
+
+ WeatherService::WeatherService(System::SystemTask& system, DateTime& dateTimeController)
+ : system(system), dateTimeController(dateTimeController) {
+ nullHeader = &nullTimelineheader;
+ nullTimelineheader->timestamp = 0;
+ }
+
+ void WeatherService::Init() {
+// uint8_t res = 0;
+// res = ble_gatts_count_cfg(serviceDefinition);
+// ASSERT(res == 0);
+//
+// res = ble_gatts_add_svcs(serviceDefinition);
+// ASSERT(res == 0);
+ }
+
+ int WeatherService::OnCommand(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt) {
+ // if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
+ // const uint8_t packetLen = OS_MBUF_PKTLEN(ctxt->om); // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+ // if (packetLen <= 0) {
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // // Decode
+ // QCBORDecodeContext decodeContext;
+ // UsefulBufC encodedCbor = {ctxt->om->om_data, OS_MBUF_PKTLEN(ctxt->om)}; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic)
+
+ // QCBORDecode_Init(&decodeContext, encodedCbor, QCBOR_DECODE_MODE_NORMAL);
+ // // KINDLY provide us a fixed-length map
+ // QCBORDecode_EnterMap(&decodeContext, nullptr);
+ // // Always encodes to the smallest number of bytes based on the value
+ // int64_t tmpTimestamp = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "Timestamp", &tmpTimestamp);
+ // if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // int64_t tmpExpires = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "Expires", &tmpExpires);
+ // if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS || tmpExpires < 0 || tmpExpires > 4294967295) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // int64_t tmpEventType = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "EventType", &tmpEventType);
+ // if (QCBORDecode_GetError(&decodeContext) != QCBOR_SUCCESS || tmpEventType < 0 ||
+ // tmpEventType >= static_cast(WeatherData::eventtype::Length)) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+
+ // switch (static_cast(tmpEventType)) {
+ // case WeatherData::eventtype::AirQuality: {
+ // std::unique_ptr airquality = std::make_unique();
+ // airquality->timestamp = tmpTimestamp;
+ // airquality->eventType = static_cast(tmpEventType);
+ // airquality->expires = tmpExpires;
+
+ // UsefulBufC stringBuf; // TODO: Everything ok with lifecycle here?
+ // QCBORDecode_GetTextStringInMapSZ(&decodeContext, "Polluter", &stringBuf);
+ // if (UsefulBuf_IsNULLOrEmptyC(stringBuf) != 0) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // airquality->polluter = std::string(static_cast(stringBuf.ptr), stringBuf.len);
+
+ // int64_t tmpAmount = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
+ // if (tmpAmount < 0 || tmpAmount > 4294967295) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // airquality->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ // if (!AddEventToTimeline(std::move(airquality))) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // break;
+ // }
+ // case WeatherData::eventtype::Obscuration: {
+ // std::unique_ptr obscuration = std::make_unique();
+ // obscuration->timestamp = tmpTimestamp;
+ // obscuration->eventType = static_cast(tmpEventType);
+ // obscuration->expires = tmpExpires;
+
+ // int64_t tmpType = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
+ // if (tmpType < 0 || tmpType >= static_cast(WeatherData::obscurationtype::Length)) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // obscuration->type = static_cast(tmpType);
+
+ // int64_t tmpAmount = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
+ // if (tmpAmount < 0 || tmpAmount > 65535) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // obscuration->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ // if (!AddEventToTimeline(std::move(obscuration))) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // break;
+ // }
+ // case WeatherData::eventtype::Precipitation: {
+ // std::unique_ptr precipitation = std::make_unique();
+ // precipitation->timestamp = tmpTimestamp;
+ // precipitation->eventType = static_cast(tmpEventType);
+ // precipitation->expires = tmpExpires;
+
+ // int64_t tmpType = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
+ // if (tmpType < 0 || tmpType >= static_cast(WeatherData::precipitationtype::Length)) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // precipitation->type = static_cast(tmpType);
+
+ // int64_t tmpAmount = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
+ // if (tmpAmount < 0 || tmpAmount > 255) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // precipitation->amount = tmpAmount; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ // if (!AddEventToTimeline(std::move(precipitation))) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // break;
+ // }
+ // case WeatherData::eventtype::Wind: {
+ // std::unique_ptr wind = std::make_unique();
+ // wind->timestamp = tmpTimestamp;
+ // wind->eventType = static_cast(tmpEventType);
+ // wind->expires = tmpExpires;
+
+ // int64_t tmpMin = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "SpeedMin", &tmpMin);
+ // if (tmpMin < 0 || tmpMin > 255) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // wind->speedMin = tmpMin; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ // int64_t tmpMax = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "SpeedMin", &tmpMax);
+ // if (tmpMax < 0 || tmpMax > 255) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // wind->speedMax = tmpMax; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ // int64_t tmpDMin = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "DirectionMin", &tmpDMin);
+ // if (tmpDMin < 0 || tmpDMin > 255) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // wind->directionMin = tmpDMin; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ // int64_t tmpDMax = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "DirectionMax", &tmpDMax);
+ // if (tmpDMax < 0 || tmpDMax > 255) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // wind->directionMax = tmpDMax; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ // if (!AddEventToTimeline(std::move(wind))) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // break;
+ // }
+ // case WeatherData::eventtype::Temperature: {
+ // std::unique_ptr temperature = std::make_unique();
+ // temperature->timestamp = tmpTimestamp;
+ // temperature->eventType = static_cast(tmpEventType);
+ // temperature->expires = tmpExpires;
+
+ // int64_t tmpTemperature = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "Temperature", &tmpTemperature);
+ // if (tmpTemperature < -32768 || tmpTemperature > 32767) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // temperature->temperature =
+ // static_cast(tmpTemperature); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ // int64_t tmpDewPoint = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "DewPoint", &tmpDewPoint);
+ // if (tmpDewPoint < -32768 || tmpDewPoint > 32767) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // temperature->dewPoint =
+ // static_cast(tmpDewPoint); // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ // if (!AddEventToTimeline(std::move(temperature))) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // break;
+ // }
+ // case WeatherData::eventtype::Special: {
+ // std::unique_ptr special = std::make_unique();
+ // special->timestamp = tmpTimestamp;
+ // special->eventType = static_cast(tmpEventType);
+ // special->expires = tmpExpires;
+
+ // int64_t tmpType = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "Type", &tmpType);
+ // if (tmpType < 0 || tmpType >= static_cast(WeatherData::specialtype::Length)) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // special->type = static_cast(tmpType);
+
+ // if (!AddEventToTimeline(std::move(special))) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // break;
+ // }
+ // case WeatherData::eventtype::Pressure: {
+ // std::unique_ptr pressure = std::make_unique();
+ // pressure->timestamp = tmpTimestamp;
+ // pressure->eventType = static_cast(tmpEventType);
+ // pressure->expires = tmpExpires;
+
+ // int64_t tmpPressure = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "Pressure", &tmpPressure);
+ // if (tmpPressure < 0 || tmpPressure >= 65535) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // pressure->pressure = tmpPressure; // NOLINT(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
+
+ // if (!AddEventToTimeline(std::move(pressure))) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // break;
+ // }
+ // case WeatherData::eventtype::Location: {
+ // std::unique_ptr location = std::make_unique();
+ // location->timestamp = tmpTimestamp;
+ // location->eventType = static_cast(tmpEventType);
+ // location->expires = tmpExpires;
+
+ // UsefulBufC stringBuf; // TODO: Everything ok with lifecycle here?
+ // QCBORDecode_GetTextStringInMapSZ(&decodeContext, "Location", &stringBuf);
+ // if (UsefulBuf_IsNULLOrEmptyC(stringBuf) != 0) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // location->location = std::string(static_cast(stringBuf.ptr), stringBuf.len);
+
+ // int64_t tmpAltitude = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "Altitude", &tmpAltitude);
+ // if (tmpAltitude < -32768 || tmpAltitude >= 32767) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // location->altitude = static_cast(tmpAltitude);
+
+ // int64_t tmpLatitude = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "Latitude", &tmpLatitude);
+ // if (tmpLatitude < -2147483648 || tmpLatitude >= 2147483647) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // location->latitude = static_cast(tmpLatitude);
+
+ // int64_t tmpLongitude = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "Longitude", &tmpLongitude);
+ // if (tmpLongitude < -2147483648 || tmpLongitude >= 2147483647) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // location->latitude = static_cast(tmpLongitude);
+
+ // if (!AddEventToTimeline(std::move(location))) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // break;
+ // }
+ // case WeatherData::eventtype::Clouds: {
+ // std::unique_ptr clouds = std::make_unique();
+ // clouds->timestamp = tmpTimestamp;
+ // clouds->eventType = static_cast(tmpEventType);
+ // clouds->expires = tmpExpires;
+
+ // int64_t tmpAmount = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "Amount", &tmpAmount);
+ // if (tmpAmount < 0 || tmpAmount > 255) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // clouds->amount = static_cast(tmpAmount);
+
+ // if (!AddEventToTimeline(std::move(clouds))) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // break;
+ // }
+ // case WeatherData::eventtype::Humidity: {
+ // std::unique_ptr humidity = std::make_unique();
+ // humidity->timestamp = tmpTimestamp;
+ // humidity->eventType = static_cast(tmpEventType);
+ // humidity->expires = tmpExpires;
+
+ // int64_t tmpType = 0;
+ // QCBORDecode_GetInt64InMapSZ(&decodeContext, "Humidity", &tmpType);
+ // if (tmpType < 0 || tmpType >= 255) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // humidity->humidity = static_cast(tmpType);
+
+ // if (!AddEventToTimeline(std::move(humidity))) {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // break;
+ // }
+ // default: {
+ // CleanUpQcbor(&decodeContext);
+ // return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
+ // }
+ // }
+
+ // QCBORDecode_ExitMap(&decodeContext);
+ // GetTimelineLength();
+ // TidyTimeline();
+
+ // if (QCBORDecode_Finish(&decodeContext) != QCBOR_SUCCESS) {
+ // return BLE_ATT_ERR_INSUFFICIENT_RES;
+ // }
+ // } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
+ // // Encode
+ // uint8_t buffer[64];
+ // QCBOREncodeContext encodeContext;
+ // /* TODO: This is very much still a test endpoint
+ // * it needs a characteristic UUID check
+ // * and actual implementations that show
+ // * what actually has to be read.
+ // * WARN: Consider commands not part of the API for now!
+ // */
+ // QCBOREncode_Init(&encodeContext, UsefulBuf_FROM_BYTE_ARRAY(buffer));
+ // QCBOREncode_OpenMap(&encodeContext);
+ // QCBOREncode_AddTextToMap(&encodeContext, "test", UsefulBuf_FROM_SZ_LITERAL("test"));
+ // QCBOREncode_AddInt64ToMap(&encodeContext, "test", 1ul);
+ // QCBOREncode_CloseMap(&encodeContext);
+
+ // UsefulBufC encodedEvent;
+ // auto uErr = QCBOREncode_Finish(&encodeContext, &encodedEvent);
+ // if (uErr != 0) {
+ // return BLE_ATT_ERR_INSUFFICIENT_RES;
+ // }
+ // auto res = os_mbuf_append(ctxt->om, &buffer, sizeof(buffer));
+ // if (res == 0) {
+ // return BLE_ATT_ERR_INSUFFICIENT_RES;
+ // }
+
+ // return 0;
+ // }
+ return 0;
+ }
+
+ std::unique_ptr& WeatherService::GetCurrentClouds() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Clouds && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast&>(header);
+ }
+ }
+
+ return reinterpret_cast&>(*this->nullHeader);
+ }
+
+ std::unique_ptr& WeatherService::GetCurrentObscuration() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Obscuration && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast&>(header);
+ }
+ }
+
+ return reinterpret_cast&>(*this->nullHeader);
+ }
+
+ std::unique_ptr& WeatherService::GetCurrentPrecipitation() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Precipitation && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast&>(header);
+ }
+ }
+
+ return reinterpret_cast&>(*this->nullHeader);
+ }
+
+ std::unique_ptr& WeatherService::GetCurrentWind() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Wind && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast&>(header);
+ }
+ }
+
+ return reinterpret_cast&>(*this->nullHeader);
+ }
+
+ std::unique_ptr& WeatherService::GetCurrentTemperature() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Temperature && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast&>(header);
+ }
+ }
+
+ return reinterpret_cast&>(*this->nullHeader);
+ }
+
+ std::unique_ptr& WeatherService::GetCurrentHumidity() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Humidity && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast&>(header);
+ }
+ }
+
+ return reinterpret_cast&>(*this->nullHeader);
+ }
+
+ std::unique_ptr& WeatherService::GetCurrentPressure() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Pressure && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast&>(header);
+ }
+ }
+
+ return reinterpret_cast&>(*this->nullHeader);
+ }
+
+ std::unique_ptr& WeatherService::GetCurrentLocation() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Location && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast&>(header);
+ }
+ }
+
+ return reinterpret_cast&>(*this->nullHeader);
+ }
+
+ std::unique_ptr& WeatherService::GetCurrentQuality() {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::AirQuality && IsEventStillValid(header, currentTimestamp)) {
+ return reinterpret_cast&>(header);
+ }
+ }
+
+ return reinterpret_cast&>(*this->nullHeader);
+ }
+
+ size_t WeatherService::GetTimelineLength() const {
+ return timeline.size();
+ }
+
+ bool WeatherService::AddEventToTimeline(std::unique_ptr event) {
+ if (timeline.size() == timeline.max_size()) {
+ return false;
+ }
+
+ timeline.push_back(std::move(event));
+ return true;
+ }
+
+ bool WeatherService::HasTimelineEventOfType(const WeatherData::eventtype type) const {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ for (auto&& header : timeline) {
+ if (header->eventType == type && IsEventStillValid(header, currentTimestamp)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ void WeatherService::TidyTimeline() {
+ uint64_t timeCurrent = GetCurrentUnixTimestamp();
+ timeline.erase(std::remove_if(std::begin(timeline),
+ std::end(timeline),
+ [&](std::unique_ptr const& header) {
+ return !IsEventStillValid(header, timeCurrent);
+ }),
+ std::end(timeline));
+
+ std::sort(std::begin(timeline), std::end(timeline), CompareTimelineEvents);
+ }
+
+ bool WeatherService::CompareTimelineEvents(const std::unique_ptr& first,
+ const std::unique_ptr& second) {
+ return first->timestamp > second->timestamp;
+ }
+
+ bool WeatherService::IsEventStillValid(const std::unique_ptr& uniquePtr, const uint64_t timestamp) {
+ // Not getting timestamp in isEventStillValid for more speed
+ return uniquePtr->timestamp + uniquePtr->expires >= timestamp;
+ }
+
+ uint64_t WeatherService::GetCurrentUnixTimestamp() const {
+ return std::chrono::duration_cast(dateTimeController.CurrentDateTime().time_since_epoch()).count();
+ }
+
+ int16_t WeatherService::GetTodayMinTemp() const {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ uint64_t currentDayEnd = currentTimestamp - ((24 - dateTimeController.Hours()) * 60 * 60) -
+ ((60 - dateTimeController.Minutes()) * 60) - (60 - dateTimeController.Seconds());
+ int16_t result = -32768;
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Temperature && IsEventStillValid(header, currentTimestamp) &&
+ header->timestamp < currentDayEnd &&
+ reinterpret_cast&>(header)->temperature != -32768) {
+ int16_t temperature = reinterpret_cast&>(header)->temperature;
+ if (result == -32768) {
+ result = temperature;
+ } else if (result > temperature) {
+ result = temperature;
+ } else {
+ // The temperature in this item is higher than the lowest we've found
+ }
+ }
+ }
+
+ return result;
+ }
+
+ int16_t WeatherService::GetTodayMaxTemp() const {
+ uint64_t currentTimestamp = GetCurrentUnixTimestamp();
+ uint64_t currentDayEnd = currentTimestamp - ((24 - dateTimeController.Hours()) * 60 * 60) -
+ ((60 - dateTimeController.Minutes()) * 60) - (60 - dateTimeController.Seconds());
+ int16_t result = -32768;
+ for (auto&& header : this->timeline) {
+ if (header->eventType == WeatherData::eventtype::Temperature && IsEventStillValid(header, currentTimestamp) &&
+ header->timestamp < currentDayEnd &&
+ reinterpret_cast&>(header)->temperature != -32768) {
+ int16_t temperature = reinterpret_cast&>(header)->temperature;
+ if (result == -32768) {
+ result = temperature;
+ } else if (result < temperature) {
+ result = temperature;
+ } else {
+ // The temperature in this item is lower than the highest we've found
+ }
+ }
+ }
+
+ return result;
+ }
+
+// void WeatherService::CleanUpQcbor(QCBORDecodeContext* decodeContext) {
+// QCBORDecode_ExitMap(decodeContext);
+// QCBORDecode_Finish(decodeContext);
+// }
diff --git a/sim/components/ble/weather/WeatherService.h b/sim/components/ble/weather/WeatherService.h
new file mode 100644
index 0000000..ea7560a
--- /dev/null
+++ b/sim/components/ble/weather/WeatherService.h
@@ -0,0 +1,172 @@
+/* Copyright (C) 2021 Avamander
+
+ This file is part of InfiniTime.
+
+ InfiniTime is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published
+ by the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ InfiniTime 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 General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
+#pragma once
+
+#include
+#include
+#include
+#include
+
+//#define min // workaround: nimble's min/max macros conflict with libstdc++
+//#define max
+//#include
+//#include
+//#undef max
+//#undef min
+
+#include "components/ble/weather/WeatherData.h"
+//#include "libs/QCBOR/inc/qcbor/qcbor.h"
+#include "components/datetime/DateTimeController.h"
+
+//int WeatherCallback(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt, void* arg);
+
+namespace Pinetime {
+ namespace System {
+ class SystemTask;
+ }
+ namespace Controllers {
+
+ class WeatherService {
+ public:
+ explicit WeatherService(System::SystemTask& system, DateTime& dateTimeController);
+
+ void Init();
+
+ int OnCommand(uint16_t connHandle, uint16_t attrHandle, struct ble_gatt_access_ctxt* ctxt);
+
+ /*
+ * Helper functions for quick access to currently valid data
+ */
+ std::unique_ptr& GetCurrentLocation();
+ std::unique_ptr& GetCurrentClouds();
+ std::unique_ptr& GetCurrentObscuration();
+ std::unique_ptr& GetCurrentPrecipitation();
+ std::unique_ptr& GetCurrentWind();
+ std::unique_ptr& GetCurrentTemperature();
+ std::unique_ptr& GetCurrentHumidity();
+ std::unique_ptr& GetCurrentPressure();
+ std::unique_ptr& GetCurrentQuality();
+
+ /**
+ * Searches for the current day's maximum temperature
+ * @return -32768 if there's no data, degrees Celsius times 100 otherwise
+ */
+ int16_t GetTodayMaxTemp() const;
+ /**
+ * Searches for the current day's minimum temperature
+ * @return -32768 if there's no data, degrees Celsius times 100 otherwise
+ */
+ int16_t GetTodayMinTemp() const;
+
+ /*
+ * Management functions
+ */
+ /**
+ * Adds an event to the timeline
+ * @return
+ */
+ bool AddEventToTimeline(std::unique_ptr event);
+ /**
+ * Gets the current timeline length
+ */
+ size_t GetTimelineLength() const;
+ /**
+ * Checks if an event of a certain type exists in the timeline
+ */
+ bool HasTimelineEventOfType(WeatherData::eventtype type) const;
+
+ private:
+ // 00040000-78fc-48fe-8e23-433b3a1942d0
+// static constexpr ble_uuid128_t BaseUuid() {
+// return CharUuid(0x00, 0x00);
+// }
+
+// // 0004yyxx-78fc-48fe-8e23-433b3a1942d0
+// static constexpr ble_uuid128_t CharUuid(uint8_t x, uint8_t y) {
+// return ble_uuid128_t {.u = {.type = BLE_UUID_TYPE_128},
+// .value = {0xd0, 0x42, 0x19, 0x3a, 0x3b, 0x43, 0x23, 0x8e, 0xfe, 0x48, 0xfc, 0x78, y, x, 0x04, 0x00}};
+// }
+
+// ble_uuid128_t weatherUuid {BaseUuid()};
+
+ /**
+ * Just write timeline data here.
+ *
+ * See {@link WeatherData.h} for more information.
+ */
+// ble_uuid128_t weatherDataCharUuid {CharUuid(0x00, 0x01)};
+ /**
+ * This doesn't take timeline data, provides some control over it.
+ *
+ * NOTE: Currently not supported. Companion app implementer feedback required.
+ * There's very little point in solidifying an API before we know the needs.
+ */
+// ble_uuid128_t weatherControlCharUuid {CharUuid(0x00, 0x02)};
+
+// const struct ble_gatt_chr_def characteristicDefinition[3] = {
+// {.uuid = &weatherDataCharUuid.u,
+// .access_cb = WeatherCallback,
+// .arg = this,
+// .flags = BLE_GATT_CHR_F_WRITE,
+// .val_handle = &eventHandle},
+// {.uuid = &weatherControlCharUuid.u, .access_cb = WeatherCallback, .arg = this, .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ},
+// {nullptr}};
+// const struct ble_gatt_svc_def serviceDefinition[2] = {
+// {.type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = &weatherUuid.u, .characteristics = characteristicDefinition}, {0}};
+
+ uint16_t eventHandle {};
+
+ Pinetime::System::SystemTask& system;
+ Pinetime::Controllers::DateTime& dateTimeController;
+
+ std::vector> timeline;
+ std::unique_ptr nullTimelineheader = std::make_unique();
+ std::unique_ptr* nullHeader;
+
+ /**
+ * Cleans up the timeline of expired events
+ */
+ void TidyTimeline();
+
+ /**
+ * Compares two timeline events
+ */
+ static bool CompareTimelineEvents(const std::unique_ptr& first,
+ const std::unique_ptr& second);
+
+ /**
+ * Returns current UNIX timestamp
+ */
+ uint64_t GetCurrentUnixTimestamp() const;
+
+ /**
+ * Checks if the event hasn't gone past and expired
+ *
+ * @param header timeline event to check
+ * @param currentTimestamp what's the time right now
+ * @return if the event is valid
+ */
+ static bool IsEventStillValid(const std::unique_ptr& uniquePtr, const uint64_t timestamp);
+
+ /**
+ * This is a helper function that closes a QCBOR map and decoding context cleanly
+ */
+// void CleanUpQcbor(QCBORDecodeContext* decodeContext);
+ };
+ }
+}
diff --git a/sim/components/brightness/BrightnessController.cpp b/sim/components/brightness/BrightnessController.cpp
new file mode 100644
index 0000000..7e198c4
--- /dev/null
+++ b/sim/components/brightness/BrightnessController.cpp
@@ -0,0 +1,126 @@
+#include "BrightnessController.h"
+//#include
+#include "displayapp/screens/Symbols.h"
+#include "drivers/PinMap.h"
+using namespace Pinetime::Controllers;
+
+void BrightnessController::Init() {
+ //nrf_gpio_cfg_output(PinMap::LcdBacklightLow);
+ //nrf_gpio_cfg_output(PinMap::LcdBacklightMedium);
+ //nrf_gpio_cfg_output(PinMap::LcdBacklightHigh);
+ Set(level);
+}
+
+void BrightnessController::Set(BrightnessController::Levels level) {
+ this->level = level;
+ //switch (level) {
+ // default:
+ // case Levels::High:
+ // nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
+ // nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
+ // nrf_gpio_pin_clear(PinMap::LcdBacklightHigh);
+ // break;
+ // case Levels::Medium:
+ // nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
+ // nrf_gpio_pin_clear(PinMap::LcdBacklightMedium);
+ // nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
+ // break;
+ // case Levels::Low:
+ // nrf_gpio_pin_clear(PinMap::LcdBacklightLow);
+ // nrf_gpio_pin_set(PinMap::LcdBacklightMedium);
+ // nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
+ // break;
+ // case Levels::Off:
+ // nrf_gpio_pin_set(PinMap::LcdBacklightLow);
+ // nrf_gpio_pin_set(PinMap::LcdBacklightMedium);
+ // nrf_gpio_pin_set(PinMap::LcdBacklightHigh);
+ // break;
+ //}
+}
+
+void BrightnessController::Lower() {
+ switch (level) {
+ case Levels::High:
+ Set(Levels::Medium);
+ break;
+ case Levels::Medium:
+ Set(Levels::Low);
+ break;
+ case Levels::Low:
+ Set(Levels::Off);
+ break;
+ default:
+ break;
+ }
+}
+
+void BrightnessController::Higher() {
+ switch (level) {
+ case Levels::Off:
+ Set(Levels::Low);
+ break;
+ case Levels::Low:
+ Set(Levels::Medium);
+ break;
+ case Levels::Medium:
+ Set(Levels::High);
+ break;
+ default:
+ break;
+ }
+}
+
+BrightnessController::Levels BrightnessController::Level() const {
+ return level;
+}
+
+void BrightnessController::Backup() {
+ backupLevel = level;
+}
+
+void BrightnessController::Restore() {
+ Set(backupLevel);
+}
+
+void BrightnessController::Step() {
+ switch (level) {
+ case Levels::Low:
+ Set(Levels::Medium);
+ break;
+ case Levels::Medium:
+ Set(Levels::High);
+ break;
+ case Levels::High:
+ Set(Levels::Low);
+ break;
+ default:
+ break;
+ }
+}
+
+const char* BrightnessController::GetIcon() {
+ switch (level) {
+ case Levels::Medium:
+ return Applications::Screens::Symbols::brightnessMedium;
+ case Levels::High:
+ return Applications::Screens::Symbols::brightnessHigh;
+ default:
+ break;
+ }
+ return Applications::Screens::Symbols::brightnessLow;
+}
+
+const char* BrightnessController::ToString() {
+ switch (level) {
+ case Levels::Off:
+ return "Off";
+ case Levels::Low:
+ return "Low";
+ case Levels::Medium:
+ return "Medium";
+ case Levels::High:
+ return "High";
+ default:
+ return "???";
+ }
+}
\ No newline at end of file
diff --git a/sim/components/brightness/BrightnessController.h b/sim/components/brightness/BrightnessController.h
new file mode 100644
index 0000000..0d7ac2f
--- /dev/null
+++ b/sim/components/brightness/BrightnessController.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include
+
+namespace Pinetime {
+ namespace Controllers {
+ class BrightnessController {
+ public:
+ enum class Levels { Off, Low, Medium, High };
+ void Init();
+
+ void Set(Levels level);
+ Levels Level() const;
+ void Lower();
+ void Higher();
+ void Step();
+
+ void Backup();
+ void Restore();
+
+ const char* GetIcon();
+ const char* ToString();
+
+ private:
+ Levels level = Levels::High;
+ Levels backupLevel = Levels::High;
+ };
+ }
+}
diff --git a/sim/components/firmwarevalidator/FirmwareValidator.cpp b/sim/components/firmwarevalidator/FirmwareValidator.cpp
new file mode 100644
index 0000000..63454dc
--- /dev/null
+++ b/sim/components/firmwarevalidator/FirmwareValidator.cpp
@@ -0,0 +1,21 @@
+#include "components/firmwarevalidator/FirmwareValidator.h"
+
+//#include
+//#include "drivers/InternalFlash.h"
+
+using namespace Pinetime::Controllers;
+
+bool FirmwareValidator::IsValidated() const {
+ return true; // lv_sim
+// auto* imageOkPtr = reinterpret_cast(validBitAdress);
+// return (*imageOkPtr) == validBitValue;
+}
+
+void FirmwareValidator::Validate() {
+// if (!IsValidated())
+// Pinetime::Drivers::InternalFlash::WriteWord(validBitAdress, validBitValue);
+}
+
+void FirmwareValidator::Reset() {
+// NVIC_SystemReset();
+}
diff --git a/sim/components/firmwarevalidator/FirmwareValidator.h b/sim/components/firmwarevalidator/FirmwareValidator.h
new file mode 100644
index 0000000..ce644f9
--- /dev/null
+++ b/sim/components/firmwarevalidator/FirmwareValidator.h
@@ -0,0 +1,19 @@
+#pragma once
+
+#include
+
+namespace Pinetime {
+ namespace Controllers {
+ class FirmwareValidator {
+ public:
+ void Validate();
+ bool IsValidated() const;
+
+ void Reset();
+
+ private:
+ static constexpr uint32_t validBitAdress {0x7BFE8};
+ static constexpr uint32_t validBitValue {1};
+ };
+ }
+}
diff --git a/sim/components/fs/FS.cpp b/sim/components/fs/FS.cpp
new file mode 100644
index 0000000..173995c
--- /dev/null
+++ b/sim/components/fs/FS.cpp
@@ -0,0 +1,250 @@
+#include "FS.h"
+#include
+#include
+#include
+//#include
+#include
+
+using namespace Pinetime::Controllers;
+
+//FS::FS(Pinetime::Drivers::SpiNorFlash& driver)
+// : flashDriver {driver},
+// lfsConfig {
+// .context = this,
+// .read = SectorRead,
+// .prog = SectorProg,
+// .erase = SectorErase,
+// .sync = SectorSync,
+//
+// .read_size = 16,
+// .prog_size = 8,
+// .block_size = blockSize,
+// .block_count = size / blockSize,
+// .block_cycles = 1000u,
+//
+// .cache_size = 16,
+// .lookahead_size = 16,
+//
+// .name_max = 50,
+// .attr_max = 50,
+// } {
+//}
+
+
+void FS::Init() {
+
+// // try mount
+// int err = lfs_mount(&lfs, &lfsConfig);
+//
+// // reformat if we can't mount the filesystem
+// // this should only happen on the first boot
+// if (err != LFS_ERR_OK) {
+// lfs_format(&lfs, &lfsConfig);
+// err = lfs_mount(&lfs, &lfsConfig);
+// if (err != LFS_ERR_OK) {
+// return;
+// }
+// }
+//
+//#ifndef PINETIME_IS_RECOVERY
+// VerifyResource();
+// LVGLFileSystemInit();
+//#endif
+
+}
+
+void FS::VerifyResource() {
+ // validate the resource metadata
+ resourcesValid = true;
+}
+
+int FS::FileOpen(lfs_file_t* file_p, const char* fileName, const int flags) {
+ // create the file in the current directory
+ const char *local_filename = fileName[0]=='/' ? &fileName[1] : fileName;
+ const char *mode;
+ bool flag_read = flags & LFS_O_RDONLY;
+ bool flag_write = flags & LFS_O_WRONLY;
+ bool flag_create = flags & LFS_O_CREAT;
+ if (flag_create) {
+ if (std::filesystem::exists(local_filename)) {
+ if (flag_read && flag_write) {
+ mode = "rb+";
+ } else if (flag_read) {
+ mode = "rb";
+ } else if (flag_write) {
+ mode = "wb";
+ } else {
+ assert(false); // not implemented
+ }
+ } else {
+ if (flag_read && flag_write) {
+ mode = "wb+";
+ } else if (flag_read) {
+ assert(false); // read only file not existing
+ mode = "rb";
+ } else if (flag_write) {
+ mode = "wb";
+ } else {
+ assert(false); // not implemented
+ }
+ }
+ } else {
+ if (std::filesystem::exists(local_filename)) {
+ if (flag_read && flag_write) {
+ mode = "rb+";
+ } else if (flag_read) {
+ mode = "rb";
+ } else if (flag_write) {
+ mode = "wb";
+ } else {
+ assert(false); // not implemented
+ }
+ } else {
+ return LFS_ERR_IO;
+ }
+ }
+ FILE *fptr = fopen(local_filename, mode);
+ if (fptr == nullptr) {
+ return LFS_ERR_BADF;
+ } else {
+ *file_p = fptr;
+ return LFS_ERR_OK;
+ }
+ //return lfs_file_open(&lfs, file_p, fileName, flags);
+}
+
+int FS::FileClose(lfs_file_t* file_p) {
+ return fclose(*file_p);
+ //return lfs_file_close(&lfs, file_p);
+}
+
+int FS::FileRead(lfs_file_t* file_p, uint8_t* buff, uint32_t size) {
+ return fread(buff, sizeof(uint8_t), size, *file_p);
+ //return lfs_file_read(&lfs, file_p, buff, size);
+}
+
+int FS::FileWrite(lfs_file_t* file_p, const uint8_t* buff, uint32_t size) {
+ return fwrite((void*)buff, sizeof(uint8_t), size, *file_p);
+ //return lfs_file_write(&lfs, file_p, buff, size);
+}
+
+int FS::FileSeek(lfs_file_t* file_p, uint32_t pos) {
+ return fseek(*file_p, pos, SEEK_SET);
+ //return lfs_file_seek(&lfs, file_p, pos, whence);
+}
+
+int FS::FileDelete(const char* fileName) {
+ return std::filesystem::remove(fileName);
+ //return lfs_remove(&lfs, fileName);
+}
+
+
+int FS::DirCreate(const char* path) {
+ return std::filesystem::create_directory(path);
+ //return lfs_mkdir(&lfs, path);
+}
+
+// Delete directory and all files inside
+int FS::DirDelete(const char* path) {
+ return std::filesystem::remove_all(path);
+
+ //lfs_dir_t lfs_dir;
+ //lfs_info entryInfo;
+
+ //int err;
+ //err = lfs_dir_open(&lfs, &lfs_dir, path);
+ //if (err) {
+ // return err;
+ //}
+ //while (lfs_dir_read(&lfs, &lfs_dir, &entryInfo)) {
+ // lfs_remove(&lfs, entryInfo.name);
+ //}
+ //lfs_dir_close(&lfs, &lfs_dir);
+ //return LFS_ERR_OK;
+}
+
+/*
+
+ ----------- Interface between littlefs and SpiNorFlash -----------
+
+*/
+//int FS::SectorSync(const struct lfs_config* c) {
+// return 0;
+//}
+//
+//int FS::SectorErase(const struct lfs_config* c, lfs_block_t block) {
+// Pinetime::Controllers::FS& lfs = *(static_cast(c->context));
+// const size_t address = startAddress + (block * blockSize);
+// lfs.flashDriver.SectorErase(address);
+// return lfs.flashDriver.EraseFailed() ? -1 : 0;
+//}
+//
+//int FS::SectorProg(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, const void* buffer, lfs_size_t size) {
+// Pinetime::Controllers::FS& lfs = *(static_cast(c->context));
+// const size_t address = startAddress + (block * blockSize) + off;
+// lfs.flashDriver.Write(address, (uint8_t*) buffer, size);
+// return lfs.flashDriver.ProgramFailed() ? -1 : 0;
+//}
+//
+//int FS::SectorRead(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, void* buffer, lfs_size_t size) {
+// Pinetime::Controllers::FS& lfs = *(static_cast(c->context));
+// const size_t address = startAddress + (block * blockSize) + off;
+// lfs.flashDriver.Read(address, static_cast(buffer), size);
+// return 0;
+//}
+
+/*
+
+ ----------- LVGL filesystem integration -----------
+
+*/
+
+namespace {
+ lv_fs_res_t lvglOpen(lv_fs_drv_t* drv, void* file_p, const char* path, lv_fs_mode_t mode) {
+
+ lfs_file_t* file = static_cast(file_p);
+ FS* filesys = static_cast(drv->user_data);
+ int ret = filesys->FileOpen(file, path, LFS_O_RDONLY);
+ if (ret != LFS_ERR_OK) {
+ return LV_FS_RES_FS_ERR;
+ }
+ return LV_FS_RES_OK;
+ }
+
+ lv_fs_res_t lvglClose(lv_fs_drv_t* drv, void* file_p) {
+ FS* filesys = static_cast(drv->user_data);
+ lfs_file_t* file = static_cast(file_p);
+ filesys->FileClose(file);
+
+ return LV_FS_RES_OK;
+ }
+
+ lv_fs_res_t lvglRead(lv_fs_drv_t* drv, void* file_p, void* buf, uint32_t btr, uint32_t* br) {
+ FS* filesys = static_cast(drv->user_data);
+ lfs_file_t* file = static_cast(file_p);
+ filesys->FileRead(file, static_cast(buf), btr);
+ *br = btr;
+ return LV_FS_RES_OK;
+ }
+ lv_fs_res_t lvglSeek(lv_fs_drv_t* drv, void* file_p, uint32_t pos) {
+ FS* filesys = static_cast(drv->user_data);
+ lfs_file_t* file = static_cast(file_p);
+ filesys->FileSeek(file, pos);
+ return LV_FS_RES_OK;
+ }
+}
+
+void FS::LVGLFileSystemInit() {
+ lv_fs_drv_init(&fs_drv);
+
+ fs_drv.file_size = sizeof(lfs_file_t);
+ fs_drv.letter = 'F';
+ fs_drv.open_cb = lvglOpen;
+ fs_drv.close_cb = lvglClose;
+ fs_drv.read_cb = lvglRead;
+ fs_drv.seek_cb = lvglSeek;
+
+ fs_drv.user_data = this;
+
+ lv_fs_drv_register(&fs_drv);
+}
diff --git a/sim/components/fs/FS.h b/sim/components/fs/FS.h
new file mode 100644
index 0000000..708ba30
--- /dev/null
+++ b/sim/components/fs/FS.h
@@ -0,0 +1,134 @@
+#pragma once
+
+#include
+#include
+//#include "drivers/SpiNorFlash.h"
+//#include
+#include
+
+using lfs_file_t = FILE*;
+
+// copied from src/libs/littlefs/lfs.h
+// Possible error codes, these are negative to allow
+// valid positive return values
+enum lfs_error {
+ LFS_ERR_OK = 0, // No error
+ LFS_ERR_IO = -5, // Error during device operation
+ LFS_ERR_CORRUPT = -84, // Corrupted
+ LFS_ERR_NOENT = -2, // No directory entry
+ LFS_ERR_EXIST = -17, // Entry already exists
+ LFS_ERR_NOTDIR = -20, // Entry is not a dir
+ LFS_ERR_ISDIR = -21, // Entry is a dir
+ LFS_ERR_NOTEMPTY = -39, // Dir is not empty
+ LFS_ERR_BADF = -9, // Bad file number
+ LFS_ERR_FBIG = -27, // File too large
+ LFS_ERR_INVAL = -22, // Invalid parameter
+ LFS_ERR_NOSPC = -28, // No space left on device
+ LFS_ERR_NOMEM = -12, // No more memory available
+ LFS_ERR_NOATTR = -61, // No data/attr available
+ LFS_ERR_NAMETOOLONG = -36, // File name too long
+};
+
+enum lfs_open_flags {
+ // open flags
+ LFS_O_RDONLY = 1, // Open a file as read only
+#ifndef LFS_READONLY
+ LFS_O_WRONLY = 2, // Open a file as write only
+ LFS_O_RDWR = 3, // Open a file as read and write
+ LFS_O_CREAT = 0x0100, // Create a file if it does not exist
+ LFS_O_EXCL = 0x0200, // Fail if a file already exists
+ LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size
+ LFS_O_APPEND = 0x0800, // Move to end of file on every write
+#endif
+};
+
+// File seek flags
+enum lfs_whence_flags {
+ LFS_SEEK_SET = 0, // Seek relative to an absolute position
+ LFS_SEEK_CUR = 1, // Seek relative to the current file position
+ LFS_SEEK_END = 2, // Seek relative to the end of the file
+};
+
+typedef int32_t lfs_ssize_t;
+
+namespace Pinetime {
+ namespace Controllers {
+ class FS {
+ public:
+ //FS(Pinetime::Drivers::SpiNorFlash&);
+
+ void Init();
+ void LVGLFileSystemInit();
+
+ int FileOpen(lfs_file_t* file_p, const char* fileName, const int flags);
+ int FileClose(lfs_file_t* file_p);
+ int FileRead(lfs_file_t* file_p, uint8_t* buff, uint32_t size);
+ int FileWrite(lfs_file_t* file_p, const uint8_t* buff, uint32_t size);
+ int FileSeek(lfs_file_t* file_p, uint32_t pos);
+
+ int FileDelete(const char* fileName);
+
+ //int DirOpen(const char* path, lfs_dir_t* lfs_dir);
+ //int DirClose(lfs_dir_t* lfs_dir);
+ //int DirRead(lfs_dir_t* dir, lfs_info* info);
+ //int DirRewind(lfs_dir_t* dir);
+ int DirCreate(const char* path);
+ int DirDelete(const char* path);
+
+ lfs_ssize_t GetFSSize();
+ int Rename(const char* oldPath, const char* newPath);
+ //int Stat(const char* path, lfs_info* info);
+ void VerifyResource();
+
+ static size_t getSize() {
+ return size;
+ }
+ static size_t getBlockSize() {
+ return blockSize;
+ }
+
+ private:
+
+ //Pinetime::Drivers::SpiNorFlash& flashDriver;
+
+ /*
+ * External Flash MAP (4 MBytes)
+ *
+ * 0x000000 +---------------------------------------+
+ * | Bootloader Assets |
+ * | 256 KBytes |
+ * | |
+ * 0x040000 +---------------------------------------+
+ * | OTA |
+ * | 464 KBytes |
+ * | |
+ * | |
+ * | |
+ * 0x0B4000 +---------------------------------------+
+ * | File System |
+ * | |
+ * | |
+ * | |
+ * | |
+ * 0x400000 +---------------------------------------+
+ *
+ */
+ static constexpr size_t startAddress = 0x0B4000;
+ static constexpr size_t size = 0x34C000;
+ static constexpr size_t blockSize = 4096;
+
+ lv_fs_drv_t fs_drv;
+
+ bool resourcesValid = false;
+ //const struct lfs_config lfsConfig;
+
+ //lfs_t lfs;
+
+ //static int SectorSync(const struct lfs_config* c);
+ //static int SectorErase(const struct lfs_config* c, lfs_block_t block);
+ //static int SectorProg(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, const void* buffer, lfs_size_t size);
+ //static int SectorRead(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, void* buffer, lfs_size_t size);
+
+ };
+ }
+}
diff --git a/sim/components/heartrate/HeartRateController.cpp b/sim/components/heartrate/HeartRateController.cpp
new file mode 100644
index 0000000..c7d0397
--- /dev/null
+++ b/sim/components/heartrate/HeartRateController.cpp
@@ -0,0 +1,35 @@
+#include "components/heartrate/HeartRateController.h"
+#include
+#include "systemtask/SystemTask.h"
+
+using namespace Pinetime::Controllers;
+
+void HeartRateController::Update(HeartRateController::States newState, uint8_t heartRate) {
+ this->state = newState;
+ if (this->heartRate != heartRate) {
+ this->heartRate = heartRate;
+ //service->OnNewHeartRateValue(heartRate);
+ }
+}
+
+void HeartRateController::Start() {
+ if (task != nullptr) {
+ state = States::NotEnoughData;
+ task->PushMessage(Pinetime::Applications::HeartRateTask::Messages::StartMeasurement);
+ }
+}
+
+void HeartRateController::Stop() {
+ if (task != nullptr) {
+ state = States::Stopped;
+ task->PushMessage(Pinetime::Applications::HeartRateTask::Messages::StopMeasurement);
+ }
+}
+
+void HeartRateController::SetHeartRateTask(Pinetime::Applications::HeartRateTask* task) {
+ this->task = task;
+}
+
+//void HeartRateController::SetService(Pinetime::Controllers::HeartRateService* service) {
+// this->service = service;
+//}
diff --git a/sim/components/heartrate/HeartRateController.h b/sim/components/heartrate/HeartRateController.h
new file mode 100644
index 0000000..89feefc
--- /dev/null
+++ b/sim/components/heartrate/HeartRateController.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include
+//#include
+
+namespace Pinetime {
+ namespace Applications {
+ class HeartRateTask;
+ }
+ namespace System {
+ class SystemTask;
+ }
+ namespace Controllers {
+ class HeartRateController {
+ public:
+ enum class States { Stopped, NotEnoughData, NoTouch, Running };
+
+ HeartRateController() = default;
+ void Start();
+ void Stop();
+ void Update(States newState, uint8_t heartRate);
+
+ void SetHeartRateTask(Applications::HeartRateTask* task);
+ States State() const {
+ return state;
+ }
+ uint8_t HeartRate() const {
+ return heartRate;
+ }
+
+// void SetService(Pinetime::Controllers::HeartRateService* service);
+
+ private:
+ Applications::HeartRateTask* task = nullptr;
+ States state = States::Stopped;
+ uint8_t heartRate = 0;
+ //Pinetime::Controllers::HeartRateService* service = nullptr;
+ };
+ }
+}
\ No newline at end of file
diff --git a/sim/components/motion/MotionController.cpp b/sim/components/motion/MotionController.cpp
new file mode 100644
index 0000000..331b5df
--- /dev/null
+++ b/sim/components/motion/MotionController.cpp
@@ -0,0 +1,88 @@
+#include "components/motion/MotionController.h"
+//#include "os/os_cputime.h"
+using namespace Pinetime::Controllers;
+
+void MotionController::Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps) {
+// if (this->nbSteps != nbSteps && service != nullptr) {
+// service->OnNewStepCountValue(nbSteps);
+// }
+//
+// if (service != nullptr && (this->x != x || this->y != y || this->z != z)) {
+// service->OnNewMotionValues(x, y, z);
+// }
+
+ this->x = x;
+ this->y = y;
+ this->z = z;
+ int32_t deltaSteps = nbSteps - this->nbSteps;
+ this->nbSteps = nbSteps;
+ if (deltaSteps > 0) {
+ currentTripSteps += deltaSteps;
+ }
+}
+
+bool MotionController::Should_RaiseWake(bool isSleeping) {
+ if ((x + 335) <= 670 && z < 0) {
+ if (not isSleeping) {
+ if (y <= 0) {
+ return false;
+ } else {
+ lastYForWakeUp = 0;
+ return false;
+ }
+ }
+
+ if (y >= 0) {
+ lastYForWakeUp = 0;
+ return false;
+ }
+ if (y + 230 < lastYForWakeUp) {
+ lastYForWakeUp = y;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool MotionController::Should_ShakeWake(uint16_t thresh) {
+ return false;
+// bool wake = false;
+// auto diff = xTaskGetTickCount() - lastShakeTime;
+// lastShakeTime = xTaskGetTickCount();
+// /* Currently Polling at 10hz, If this ever goes faster scalar and EMA might need adjusting */
+// int32_t speed = std::abs(z + (y / 2) + (x / 4) - lastYForShake - lastZForShake) / diff * 100;
+// //(.2 * speed) + ((1 - .2) * accumulatedspeed);
+// // implemented without floats as .25Alpha
+// accumulatedspeed = (speed / 5) + ((accumulatedspeed / 5) * 4);
+//
+// if (accumulatedspeed > thresh) {
+// wake = true;
+// }
+// lastXForShake = x / 4;
+// lastYForShake = y / 2;
+// lastZForShake = z;
+// return wake;
+}
+int32_t MotionController::currentShakeSpeed() {
+ return accumulatedspeed;
+}
+
+void MotionController::IsSensorOk(bool isOk) {
+ isSensorOk = isOk;
+}
+void MotionController::Init(Pinetime::Drivers::Bma421::DeviceTypes types) {
+ switch (types) {
+ case Drivers::Bma421::DeviceTypes::BMA421:
+ this->deviceType = DeviceTypes::BMA421;
+ break;
+ case Drivers::Bma421::DeviceTypes::BMA425:
+ this->deviceType = DeviceTypes::BMA425;
+ break;
+ default:
+ this->deviceType = DeviceTypes::Unknown;
+ break;
+ }
+}
+//void MotionController::SetService(Pinetime::Controllers::MotionService* service) {
+// this->service = service;
+//}
diff --git a/sim/components/motion/MotionController.h b/sim/components/motion/MotionController.h
new file mode 100644
index 0000000..a16560b
--- /dev/null
+++ b/sim/components/motion/MotionController.h
@@ -0,0 +1,72 @@
+#pragma once
+
+#include
+#include
+//#include
+
+namespace Pinetime {
+ namespace Controllers {
+ class MotionController {
+ public:
+ enum class DeviceTypes{
+ Unknown,
+ BMA421,
+ BMA425,
+ };
+
+ void Update(int16_t x, int16_t y, int16_t z, uint32_t nbSteps);
+
+ int16_t X() const {
+ return x;
+ }
+ int16_t Y() const {
+ return y;
+ }
+ int16_t Z() const {
+ return z;
+ }
+ uint32_t NbSteps() const {
+ return nbSteps;
+ }
+
+ void ResetTrip() {
+ currentTripSteps = 0;
+ }
+ uint32_t GetTripSteps() const {
+ return currentTripSteps;
+ }
+
+ bool Should_ShakeWake(uint16_t thresh);
+ bool Should_RaiseWake(bool isSleeping);
+ int32_t currentShakeSpeed();
+ void IsSensorOk(bool isOk);
+ bool IsSensorOk() const {
+ return isSensorOk;
+ }
+
+ DeviceTypes DeviceType() const {
+ return deviceType;
+ }
+
+ void Init(Pinetime::Drivers::Bma421::DeviceTypes types);
+// void SetService(Pinetime::Controllers::MotionService* service);
+
+ private:
+ uint32_t nbSteps;
+ uint32_t currentTripSteps = 0;
+ int16_t x;
+ int16_t y;
+ int16_t z;
+ int16_t lastYForWakeUp = 0;
+ bool isSensorOk = false;
+ DeviceTypes deviceType = DeviceTypes::Unknown;
+// Pinetime::Controllers::MotionService* service = nullptr;
+
+ int16_t lastXForShake = 0;
+ int16_t lastYForShake = 0;
+ int16_t lastZForShake = 0;
+ int32_t accumulatedspeed = 0;
+ uint32_t lastShakeTime = 0;
+ };
+ }
+}
\ No newline at end of file
diff --git a/sim/components/motor/MotorController.cpp b/sim/components/motor/MotorController.cpp
new file mode 100644
index 0000000..355a630
--- /dev/null
+++ b/sim/components/motor/MotorController.cpp
@@ -0,0 +1,58 @@
+#include "components/motor/MotorController.h"
+
+#include
+
+using namespace Pinetime::Controllers;
+
+void MotorController::Init() {
+ //nrf_gpio_cfg_output(PinMap::Motor);
+ //nrf_gpio_pin_set(PinMap::Motor);
+ //app_timer_init();
+
+ //app_timer_create(&shortVibTimer, APP_TIMER_MODE_SINGLE_SHOT, StopMotor);
+ //app_timer_create(&longVibTimer, APP_TIMER_MODE_REPEATED, Ring);
+}
+
+void MotorController::Ring(void* p_context) {
+ auto* motorController = static_cast(p_context);
+ motorController->RunForDuration(50);
+}
+
+Uint32 StopMotor_callback(Uint32 interval, void *param)
+{
+ auto* motorController = static_cast(param);
+ motorController->motor_is_running = false;
+ return 0; // cancel timer
+}
+Uint32 Ring_callback(Uint32 interval, void *param)
+{
+ auto* motorController = static_cast(param);
+ motorController->RunForDuration(50);
+ if (motorController->is_ringing) {
+ return interval;
+ }
+ return 0;
+}
+void MotorController::RunForDuration(uint8_t motorDuration) {
+ this->motor_is_running = true;
+ SDL_AddTimer(motorDuration, StopMotor_callback, this);
+ //nrf_gpio_pin_clear(PinMap::Motor);
+ //app_timer_start(shortVibTimer, APP_TIMER_TICKS(motorDuration), nullptr);
+}
+
+void MotorController::StartRinging() {
+ Ring(this);
+ is_ringing = true;
+ SDL_AddTimer(1000, Ring_callback, this);
+ //app_timer_start(longVibTimer, APP_TIMER_TICKS(1000), this);
+}
+
+void MotorController::StopRinging() {
+ is_ringing = false;
+}
+
+void MotorController::StopMotor(void* p_context) {
+ //nrf_gpio_pin_set(PinMap::Motor);
+ auto* motorController = static_cast(p_context);
+ motorController->motor_is_running = false;
+}
diff --git a/sim/components/motor/MotorController.h b/sim/components/motor/MotorController.h
new file mode 100644
index 0000000..b9b964c
--- /dev/null
+++ b/sim/components/motor/MotorController.h
@@ -0,0 +1,25 @@
+#pragma once
+
+#include
+
+namespace Pinetime {
+ namespace Controllers {
+
+ class MotorController {
+ public:
+ MotorController() = default;
+
+ void Init();
+ void RunForDuration(uint8_t motorDuration);
+ void StartRinging();
+ void StopRinging();
+
+ bool motor_is_running = false;
+ bool is_ringing = false;
+
+ private:
+ static void Ring(void* p_context);
+ static void StopMotor(void* p_context);
+ };
+ }
+}
diff --git a/sim/displayapp/LittleVgl.cpp b/sim/displayapp/LittleVgl.cpp
new file mode 100644
index 0000000..9172fdd
--- /dev/null
+++ b/sim/displayapp/LittleVgl.cpp
@@ -0,0 +1,327 @@
+#include "displayapp/LittleVgl.h"
+#include "displayapp/lv_pinetime_theme.h"
+
+//#include
+//#include
+////#include
+#include "drivers/Cst816s.h"
+#include "drivers/St7789.h"
+
+using namespace Pinetime::Components;
+
+//lv_style_t* LabelBigStyle = nullptr;
+//
+//static void disp_flush(lv_disp_drv_t* disp_drv, const lv_area_t* area, lv_color_t* color_p) {
+// auto* lvgl = static_cast(disp_drv->user_data);
+// lvgl->FlushDisplay(area, color_p);
+//}
+//
+bool touchpad_read(lv_indev_drv_t* indev_drv, lv_indev_data_t* data) {
+ auto* lvgl = static_cast(indev_drv->user_data);
+ return lvgl->GetTouchPadInfo(data);
+}
+
+LittleVgl::LittleVgl(Pinetime::Drivers::St7789& lcd, Pinetime::Drivers::Cst816S& touchPanel)
+ : lcd {lcd}, touchPanel {touchPanel}, previousClick {0, 0} {
+
+}
+
+void LittleVgl::Init() {
+// lv_init();
+// InitDisplay();
+// InitTheme();
+ InitTouchpad();
+}
+
+//void LittleVgl::InitDisplay() {
+// lv_disp_draw_buf_init(&disp_buf_2, buf2_1, buf2_2, LV_HOR_RES_MAX * 4); /*Initialize the display buffer*/
+// lv_disp_drv_init(&disp_drv); /*Basic initialization*/
+//
+// /*Set up the functions to access to your display*/
+//
+// /*Set the resolution of the display*/
+// disp_drv.hor_res = 240;
+// disp_drv.ver_res = 240;
+//
+// /*Used to copy the buffer's content to the display*/
+// disp_drv.flush_cb = disp_flush;
+// /*Set a display buffer*/
+// disp_drv.draw_buf = &disp_buf_2;
+// disp_drv.user_data = this;
+//
+// /*Finally register the driver*/
+// lv_disp_drv_register(&disp_drv);
+//}
+
+void LittleVgl::InitTouchpad() {
+ lv_indev_drv_t indev_drv;
+
+ lv_indev_drv_init(&indev_drv);
+ indev_drv.type = LV_INDEV_TYPE_POINTER;
+ indev_drv.read_cb = touchpad_read;
+ indev_drv.user_data = this;
+ lv_indev_drv_register(&indev_drv);
+}
+
+void LittleVgl::SetFullRefresh(FullRefreshDirections direction) {
+ if (scrollDirection == FullRefreshDirections::None) {
+ scrollDirection = direction;
+ if (scrollDirection == FullRefreshDirections::Down) {
+ lv_disp_set_rotation(lv_disp_get_default(), LV_DISP_ROT_NONE);
+ } else if (scrollDirection == FullRefreshDirections::Right) {
+ lv_disp_set_rotation(lv_disp_get_default(), LV_DISP_ROT_NONE);
+ } else if (scrollDirection == FullRefreshDirections::Left) {
+ lv_disp_set_rotation(lv_disp_get_default(), LV_DISP_ROT_NONE);
+ } else if (scrollDirection == FullRefreshDirections::RightAnim) {
+ lv_disp_set_rotation(lv_disp_get_default(), LV_DISP_ROT_NONE);
+ } else if (scrollDirection == FullRefreshDirections::LeftAnim) {
+ lv_disp_set_rotation(lv_disp_get_default(), LV_DISP_ROT_NONE);
+ }
+ }
+}
+//
+//
+//void LittleVgl::DisplayDownScroll(){
+// // We are controlling the drawing process, disable lvgl timers
+// lv_timer_enable(false);
+//
+// // For each segment, draw the full width, 4 lines at a time starting from the bottom
+// // TODO: Should probably calculate this from the size of the draw buffer
+// int16_t height = 4;
+// int16_t width = 240;
+// int16_t y2 = 240;
+// int16_t y1 = 240 - height;
+//
+// lv_area_t area;
+// area.x1 = 0;
+// area.x2 = width;
+//
+// // Start from the bottom and create a 4 line high box
+// for (y1 = 240 - height; y1 >= 0; y1 -= height) {
+// y2 = y1 + height - 1;
+//
+// // If the box has reached the end of the visible line on the lcd controller...
+// if (y2 == visibleNbLines - 1) {
+// // move past the non visible lines
+// writeOffset += (totalNbLines - visibleNbLines);
+// // and wrap around to the start of address space
+// writeOffset %= totalNbLines;
+// }
+// // Set new box
+// area.y1 = y1;
+// area.y2 = y2;
+//
+// // Scroll as we draw
+// uint16_t toScroll = height;
+// if (scrollOffset >= toScroll)
+// scrollOffset -= toScroll;
+// else { // now we need to wrap the scroll address
+// toScroll -= scrollOffset;
+// scrollOffset = totalNbLines - toScroll;
+// }
+// lcd.VerticalScrollStartAddress(scrollOffset);
+//
+// lv_disp_t* disp = lv_disp_get_default();
+// // Clear invalid area list / tells lvgl that nothing on the screen needs to be updated
+// _lv_inv_area(disp, nullptr);
+// // invalidate only the segment we want to update in this portion of the animation
+// _lv_inv_area(disp, &area);
+// // cancel any current flushes in the display driver
+// // Since we've stopped timers, it will be waiting forever if there is currently a flush
+// lv_disp_flush_ready(disp->driver);
+// lv_refr_now(disp);
+// }
+// // Done! clear flags and enable timers
+// scrollDirection = FullRefreshDirections::None;
+// animating = false;
+// lv_timer_enable(true);
+//}
+//
+//void LittleVgl::DisplayHorizAnim() {
+// lv_timer_enable(false);
+//
+// int16_t height, width, x1, x2;
+// lv_area_t area;
+//
+// height = 240;
+// width = 4;
+// int16_t (*NextStep)(int16_t, int16_t){};
+// bool (*CheckEnd)(int16_t){};
+//
+// area.y1=0;
+// area.y2=height;
+//
+// if (scrollDirection == FullRefreshDirections::RightAnim) {
+// x1 = 0;
+//
+// CheckEnd = [](int16_t x) -> bool {
+// return (x < LV_HOR_RES_MAX);
+// };
+// NextStep = [](int16_t x, int16_t width) -> int16_t {
+// auto newx = x + width * 2;
+// if (newx < 240) {return newx;};
+// return (newx < 240 + width) ? (newx - 240 + width) : newx;
+// };
+//
+// } else if (scrollDirection == FullRefreshDirections::LeftAnim) {
+// x1 = 240 - width;
+//
+// CheckEnd = [](int16_t x) -> bool {
+// return (x >= 0);
+// };
+// NextStep = [](int16_t x, int16_t width) -> int16_t {
+// auto newx = x - width * 2;
+// if (newx >= 0) {return newx;}
+// return (newx >= 0 - width) ? (newx + 240 - width) : newx;
+// };
+//
+// } else {
+// // Not set for a horizontal animation!
+// lv_timer_enable(true);
+// return;
+// }
+//
+// for (; CheckEnd(x1); x1 = NextStep(x1, width)) {
+// x2 = x1 + width-1;
+//
+// if (area.y2 == visibleNbLines - 1) {
+// writeOffset += (totalNbLines - visibleNbLines);
+// writeOffset %= totalNbLines;
+// }
+// area.x1 = x1;
+// area.x2 = x2;
+//
+// lv_disp_t* disp = lv_disp_get_default();
+// _lv_inv_area(disp, nullptr);
+// _lv_inv_area(disp, &area);
+// lv_disp_flush_ready(disp->driver);
+// lv_refr_now(disp);
+// }
+// scrollDirection = FullRefreshDirections::None;
+// animating = false;
+// lv_timer_enable(true);
+//}
+//
+//void LittleVgl::FlushDisplayManually() {
+// switch(scrollDirection){
+// case FullRefreshDirections::Down:
+// DisplayDownScroll();
+// break;
+// case FullRefreshDirections::RightAnim:
+// case FullRefreshDirections::LeftAnim:
+// DisplayHorizAnim();
+// break;
+// default:
+// break;
+// }
+//}
+//
+void LittleVgl::FlushDisplay(const lv_area_t* area, lv_color_t* color_p) {
+// uint16_t y1, y2, width, height = 0;
+//
+// ulTaskNotifyTake(pdTRUE, 200);
+// // NOtification is still needed (even if there is a mutex on SPI) because of the DataCommand pin
+// // which cannot be set/clear during a transfert.
+//
+// if (!animating && (scrollDirection == FullRefreshDirections::Down ||
+// scrollDirection == FullRefreshDirections::RightAnim ||
+// scrollDirection == FullRefreshDirections::LeftAnim)){
+// animating = true;
+// FlushDisplayManually();
+// return;
+// }
+//
+// if ((scrollDirection == FullRefreshDirections::Up) && (area->y1 == 0)) {
+// writeOffset = (writeOffset + visibleNbLines) % totalNbLines;
+// }
+//
+// y1 = (area->y1 + writeOffset) % totalNbLines;
+// y2 = (area->y2 + writeOffset) % totalNbLines;
+//
+// width = (area->x2 - area->x1) + 1;
+// height = (area->y2 - area->y1) + 1;
+//
+// if (scrollDirection == FullRefreshDirections::Up) {
+//
+// if (area->y1 > 0) {
+// if (area->y2 == visibleNbLines - 1) {
+// scrollOffset += (height * 2);
+// scrollDirection = FullRefreshDirections::None;
+//// lv_disp_set_rotation(lv_disp_get_default(), LV_DISP_ROT_NONE);
+// } else {
+// scrollOffset += height;
+// }
+// scrollOffset = scrollOffset % totalNbLines;
+// lcd.VerticalScrollStartAddress(scrollOffset);
+// }
+// } else if (scrollDirection == FullRefreshDirections::Left or scrollDirection == FullRefreshDirections::LeftAnim) {
+// if (area->x2 == visibleNbLines - 1) {
+// scrollDirection = FullRefreshDirections::None;
+//// lv_disp_set_rotation(lv_disp_get_default(), LV_DISP_ROT_NONE);
+// }
+// } else if (scrollDirection == FullRefreshDirections::Right or scrollDirection == FullRefreshDirections::RightAnim) {
+// if (area->x1 == 0) {
+// scrollDirection = FullRefreshDirections::None;
+//// lv_disp_set_rotation(lv_disp_get_default(), LV_DISP_ROT_NONE);
+// }
+// }
+//
+// if (y2 < y1) {
+// height = totalNbLines - y1;
+//
+// if (height > 0) {
+// lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast(color_p), width * height * 2);
+// ulTaskNotifyTake(pdTRUE, 100);
+// }
+//
+// uint16_t pixOffset = width * height;
+// height = y2 + 1;
+// lcd.DrawBuffer(area->x1, 0, width, height, reinterpret_cast(color_p + pixOffset), width * height * 2);
+//
+// } else {
+// lcd.DrawBuffer(area->x1, y1, width, height, reinterpret_cast(color_p), width * height * 2);
+// }
+//
+// // IMPORTANT!!!
+// // Inform the graphics library that you are ready with the flushing
+// lv_disp_flush_ready(&disp_drv);
+
+ lv_disp_t *disp = lv_disp_get_default();
+ lv_disp_drv_t *disp_drv = &disp->driver;
+ lv_area_t area_trimmed = *area;
+ if (area->x1 < 0)
+ area_trimmed.x1 = 0;
+ if (area->x2 >= LV_HOR_RES)
+ area_trimmed.x2 = LV_HOR_RES-1;
+ if (area->y1 < 0)
+ area_trimmed.y1 = 0;
+ if (area->y2 >= LV_VER_RES)
+ area_trimmed.y2 = LV_VER_RES-1;
+ // tell flush_cb this is the last thing to flush to get the monitor refreshed
+ lv_disp_get_buf(disp)->flushing_last = true;
+ disp_drv->flush_cb(disp_drv, &area_trimmed, color_p);
+}
+
+void LittleVgl::SetNewTouchPoint(uint16_t x, uint16_t y, bool contact) {
+ tap_x = x;
+ tap_y = y;
+ tapped = contact;
+}
+
+bool LittleVgl::GetTouchPadInfo(lv_indev_data_t* ptr) {
+ ptr->point.x = tap_x;
+ ptr->point.y = tap_y;
+ if (tapped) {
+ ptr->state = LV_INDEV_STATE_PR;
+ } else {
+ ptr->state = LV_INDEV_STATE_REL;
+ }
+ return false;
+}
+
+//void LittleVgl::InitTheme() {
+// if (!lv_pinetime_theme_is_inited()) {
+// lv_theme_t* th = lv_pinetime_theme_init(lv_disp_get_default(), lv_color_white(), lv_color_hex(0xC0C0C0), &jetbrains_mono_bold_20);
+// lv_disp_set_theme(lv_disp_get_default(), th);
+// }
+//}
diff --git a/sim/displayapp/LittleVgl.h b/sim/displayapp/LittleVgl.h
new file mode 100644
index 0000000..db47881
--- /dev/null
+++ b/sim/displayapp/LittleVgl.h
@@ -0,0 +1,64 @@
+#pragma once
+
+#include
+
+namespace Pinetime {
+ namespace Drivers {
+ class Cst816S;
+ class St7789;
+ }
+
+ namespace Components {
+ class LittleVgl {
+ public:
+ enum class FullRefreshDirections { None, Up, Down, Left, Right, LeftAnim, RightAnim };
+ LittleVgl(Pinetime::Drivers::St7789& lcd, Pinetime::Drivers::Cst816S& touchPanel);
+
+ LittleVgl(const LittleVgl&) = delete;
+ LittleVgl& operator=(const LittleVgl&) = delete;
+ LittleVgl(LittleVgl&&) = delete;
+ LittleVgl& operator=(LittleVgl&&) = delete;
+
+ void Init();
+
+ void FlushDisplay(const lv_area_t* area, lv_color_t* color_p);
+ bool GetTouchPadInfo(lv_indev_data_t* ptr);
+ void SetFullRefresh(FullRefreshDirections direction);
+ void SetNewTouchPoint(uint16_t x, uint16_t y, bool contact);
+//
+// private:
+// void InitDisplay();
+ void InitTouchpad();
+// void InitTheme();
+//
+// void FlushDisplayManually();
+// void DisplayDownScroll();
+// void DisplayHorizAnim();
+
+ Pinetime::Drivers::St7789& lcd;
+ Pinetime::Drivers::Cst816S& touchPanel;
+
+// lv_disp_draw_buf_t disp_buf_2;
+// lv_color_t buf2_1[LV_HOR_RES_MAX * 4];
+// lv_color_t buf2_2[LV_HOR_RES_MAX * 4];
+//
+ lv_disp_drv_t disp_drv;
+ lv_point_t previousClick;
+
+ bool firstTouch = true;
+ static constexpr uint8_t nbWriteLines = 4;
+ static constexpr uint16_t totalNbLines = 320;
+ static constexpr uint16_t visibleNbLines = 240;
+ static constexpr uint8_t MaxScrollOffset() {
+ return LV_VER_RES_MAX - nbWriteLines;
+ }
+ FullRefreshDirections scrollDirection = FullRefreshDirections::None;
+ uint16_t writeOffset = 0;
+ uint16_t scrollOffset = 0;
+
+ uint16_t tap_x = 0;
+ uint16_t tap_y = 0;
+ bool tapped = false;
+ };
+ }
+}
diff --git a/sim/displayapp/screens/Missing.cpp b/sim/displayapp/screens/Missing.cpp
new file mode 100644
index 0000000..d671de8
--- /dev/null
+++ b/sim/displayapp/screens/Missing.cpp
@@ -0,0 +1,131 @@
+#include "displayapp/screens/Missing.h"
+#include "displayapp/DisplayApp.h"
+
+using namespace Pinetime::Applications::Screens;
+
+Missing::Missing(Pinetime::Applications::DisplayApp* app, Pinetime::Applications::Apps app_key) : Screen(app) {
+ const char *screen_lbl;
+ switch (app_key) {
+ case Pinetime::Applications::Apps::None:
+ screen_lbl = "None";
+ break;
+ case Pinetime::Applications::Apps::Launcher:
+ screen_lbl = "Launcher";
+ break;
+ case Pinetime::Applications::Apps::Clock:
+ screen_lbl = "Clock";
+ break;
+ case Pinetime::Applications::Apps::SysInfo:
+ screen_lbl = "SysInfo";
+ break;
+ case Pinetime::Applications::Apps::FirmwareUpdate:
+ screen_lbl = "FirmwareUpdate";
+ break;
+ case Pinetime::Applications::Apps::FirmwareValidation:
+ screen_lbl = "FirmwareValidation";
+ break;
+ case Pinetime::Applications::Apps::NotificationsPreview:
+ screen_lbl = "NotificationPreview";
+ break;
+ case Pinetime::Applications::Apps::Notifications:
+ screen_lbl = "Notifications";
+ break;
+ case Pinetime::Applications::Apps::Timer:
+ screen_lbl = "Timer";
+ break;
+ case Pinetime::Applications::Apps::Alarm:
+ screen_lbl = "Alarm";
+ break;
+ case Pinetime::Applications::Apps::FlashLight:
+ screen_lbl = "FlashLight";
+ break;
+ case Pinetime::Applications::Apps::BatteryInfo:
+ screen_lbl = "BatteryInfo";
+ break;
+ case Pinetime::Applications::Apps::Music:
+ screen_lbl = "Music";
+ break;
+ case Pinetime::Applications::Apps::Paint:
+ screen_lbl = "Paint";
+ break;
+ case Pinetime::Applications::Apps::Paddle:
+ screen_lbl = "Paddle";
+ break;
+ case Pinetime::Applications::Apps::Twos:
+ screen_lbl = "Twos";
+ break;
+ case Pinetime::Applications::Apps::HeartRate:
+ screen_lbl = "HeartRate";
+ break;
+ case Pinetime::Applications::Apps::Navigation:
+ screen_lbl = "Navigation";
+ break;
+ case Pinetime::Applications::Apps::StopWatch:
+ screen_lbl = "StopWatch";
+ break;
+ case Pinetime::Applications::Apps::Metronome:
+ screen_lbl = "Metronome";
+ break;
+ case Pinetime::Applications::Apps::Motion:
+ screen_lbl = "Motion";
+ break;
+ case Pinetime::Applications::Apps::Steps:
+ screen_lbl = "Steps";
+ break;
+ case Pinetime::Applications::Apps::Weather:
+ screen_lbl = "Weather";
+ break;
+ case Pinetime::Applications::Apps::PassKey:
+ screen_lbl = "PassKey";
+ break;
+ case Pinetime::Applications::Apps::QuickSettings:
+ screen_lbl = "QuickSettings";
+ break;
+ case Pinetime::Applications::Apps::Settings:
+ screen_lbl = "Settings";
+ break;
+ case Pinetime::Applications::Apps::SettingWatchFace:
+ screen_lbl = "SettingWatchFace";
+ break;
+ case Pinetime::Applications::Apps::SettingTimeFormat:
+ screen_lbl = "SettingTimeFormat";
+ break;
+ case Pinetime::Applications::Apps::SettingDisplay:
+ screen_lbl = "SettingDisplay";
+ break;
+ case Pinetime::Applications::Apps::SettingWakeUp:
+ screen_lbl = "SettingWakeUp";
+ break;
+ case Pinetime::Applications::Apps::SettingSteps:
+ screen_lbl = "SettingSteps";
+ break;
+ case Pinetime::Applications::Apps::SettingSetDate:
+ screen_lbl = "SettingSetDate";
+ break;
+ case Pinetime::Applications::Apps::SettingSetTime:
+ screen_lbl = "SettingSetTime";
+ break;
+ case Pinetime::Applications::Apps::SettingChimes:
+ screen_lbl = "SettingChimes";
+ break;
+ case Pinetime::Applications::Apps::SettingShakeThreshold:
+ screen_lbl = "SettingThreshold";
+ break;
+ case Pinetime::Applications::Apps::Error:
+ screen_lbl = "Error";
+ break;
+ //case Pinetime::Applications::Apps::Weather:
+ // screen_lbl = "Weather";
+ // break;
+ default:
+ screen_lbl = "unkown screen";
+ }
+ lv_obj_t* label = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text_static(label, screen_lbl);
+ lv_obj_align(label, nullptr, LV_ALIGN_CENTER, 0, -20);
+}
+
+Missing::~Missing() {
+ lv_obj_clean(lv_scr_act());
+}
+
diff --git a/sim/displayapp/screens/Missing.h b/sim/displayapp/screens/Missing.h
new file mode 100644
index 0000000..a858dde
--- /dev/null
+++ b/sim/displayapp/screens/Missing.h
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "displayapp/screens/Screen.h"
+#include "displayapp/Apps.h"
+#include
+
+namespace Pinetime {
+ namespace Applications {
+ namespace Screens {
+
+ class Missing : public Screen {
+ public:
+ Missing(DisplayApp* app, Pinetime::Applications::Apps app_key);
+ ~Missing() override;
+ };
+ }
+ }
+}
diff --git a/sim/drivers/Bma421.cpp b/sim/drivers/Bma421.cpp
new file mode 100644
index 0000000..f2d3cdd
--- /dev/null
+++ b/sim/drivers/Bma421.cpp
@@ -0,0 +1,134 @@
+#include "drivers/Bma421.h"
+#include
+#include
+#include "drivers/TwiMaster.h"
+#include
+
+using namespace Pinetime::Drivers;
+
+namespace {
+// int8_t user_i2c_read(uint8_t reg_addr, uint8_t* reg_data, uint32_t length, void* intf_ptr) {
+// auto bma421 = static_cast(intf_ptr);
+// bma421->Read(reg_addr, reg_data, length);
+// return 0;
+// }
+//
+// int8_t user_i2c_write(uint8_t reg_addr, const uint8_t* reg_data, uint32_t length, void* intf_ptr) {
+// auto bma421 = static_cast(intf_ptr);
+// bma421->Write(reg_addr, reg_data, length);
+// return 0;
+// }
+//
+// void user_delay(uint32_t period_us, void* intf_ptr) {
+// nrf_delay_us(period_us);
+// }
+}
+
+Bma421::Bma421(TwiMaster& twiMaster, uint8_t twiAddress) : twiMaster {twiMaster}, deviceAddress {twiAddress} {
+// bma.intf = BMA4_I2C_INTF;
+// bma.bus_read = user_i2c_read;
+// bma.bus_write = user_i2c_write;
+// bma.variant = BMA42X_VARIANT;
+// bma.intf_ptr = this;
+// bma.delay_us = user_delay;
+// bma.read_write_len = 16;
+}
+
+void Bma421::Init() {
+ if (not isResetOk)
+ return; // Call SoftReset (and reset TWI device) first!
+
+// auto ret = bma423_init(&bma);
+// if (ret != BMA4_OK)
+// return;
+
+ switch(bma.chip_id) {
+ case BMA423_CHIP_ID: deviceType = DeviceTypes::BMA421; break;
+ case BMA425_CHIP_ID: deviceType = DeviceTypes::BMA425; break;
+ default: deviceType = DeviceTypes::Unknown; break;
+ }
+
+// ret = bma423_write_config_file(&bma);
+// if (ret != BMA4_OK)
+// return;
+//
+// ret = bma4_set_interrupt_mode(BMA4_LATCH_MODE, &bma);
+// if (ret != BMA4_OK)
+// return;
+//
+// ret = bma423_feature_enable(BMA423_STEP_CNTR, 1, &bma);
+// if (ret != BMA4_OK)
+// return;
+//
+// ret = bma423_step_detector_enable(0, &bma);
+// if (ret != BMA4_OK)
+// return;
+//
+// ret = bma4_set_accel_enable(1, &bma);
+// if (ret != BMA4_OK)
+// return;
+//
+// struct bma4_accel_config accel_conf;
+// accel_conf.odr = BMA4_OUTPUT_DATA_RATE_100HZ;
+// accel_conf.range = BMA4_ACCEL_RANGE_2G;
+// accel_conf.bandwidth = BMA4_ACCEL_NORMAL_AVG4;
+// accel_conf.perf_mode = BMA4_CIC_AVG_MODE;
+// ret = bma4_set_accel_config(&accel_conf, &bma);
+// if (ret != BMA4_OK)
+// return;
+//
+ isOk = true;
+}
+
+void Bma421::Reset() {
+ uint8_t data = 0xb6;
+ twiMaster.Write(deviceAddress, 0x7E, &data, 1);
+}
+
+void Bma421::Read(uint8_t registerAddress, uint8_t* buffer, size_t size) {
+ twiMaster.Read(deviceAddress, registerAddress, buffer, size);
+}
+
+void Bma421::Write(uint8_t registerAddress, const uint8_t* data, size_t size) {
+ twiMaster.Write(deviceAddress, registerAddress, data, size);
+}
+
+Bma421::Values Bma421::Process() {
+ if (not isOk)
+ return {};
+// struct bma4_accel data;
+// bma4_read_accel_xyz(&data, &bma);
+//
+// uint32_t steps = 0;
+// bma423_step_counter_output(&steps, &bma);
+//
+// int32_t temperature = 0;
+// bma4_get_temperature(&temperature, BMA4_DEG, &bma);
+// temperature = temperature / 1000;
+//
+// uint8_t activity = 0;
+// bma423_activity_output(&activity, &bma);
+//
+// // X and Y axis are swapped because of the way the sensor is mounted in the PineTime
+// return {steps, data.y, data.x, data.z};
+ return {steps, 0, 0, 0};
+}
+bool Bma421::IsOk() const {
+ return isOk;
+}
+
+void Bma421::ResetStepCounter() {
+// bma423_reset_step_counter(&bma);
+ steps = 0;
+}
+
+void Bma421::SoftReset() {
+// auto ret = bma4_soft_reset(&bma);
+// if (ret == BMA4_OK) {
+// isResetOk = true;
+// nrf_delay_ms(1);
+// }
+}
+Bma421::DeviceTypes Bma421::DeviceType() const {
+ return deviceType;
+}
diff --git a/sim/drivers/Bma421.h b/sim/drivers/Bma421.h
new file mode 100644
index 0000000..b532749
--- /dev/null
+++ b/sim/drivers/Bma421.h
@@ -0,0 +1,56 @@
+#pragma once
+#include
+#include
+#include
+#include
+
+namespace Pinetime {
+ namespace Drivers {
+ class TwiMaster;
+ class Bma421 {
+ public:
+ enum class DeviceTypes : uint8_t {
+ Unknown,
+ BMA421,
+ BMA425
+ };
+ struct Values {
+ uint32_t steps;
+ int16_t x;
+ int16_t y;
+ int16_t z;
+ };
+ Bma421(TwiMaster& twiMaster, uint8_t twiAddress);
+ Bma421(const Bma421&) = delete;
+ Bma421& operator=(const Bma421&) = delete;
+ Bma421(Bma421&&) = delete;
+ Bma421& operator=(Bma421&&) = delete;
+
+ /// The chip freezes the TWI bus after the softreset operation. Softreset is separated from the
+ /// Init() method to allow the caller to uninit and then reinit the TWI device after the softreset.
+ void SoftReset();
+ void Init();
+ Values Process();
+ void ResetStepCounter();
+
+ void Read(uint8_t registerAddress, uint8_t* buffer, size_t size);
+ void Write(uint8_t registerAddress, const uint8_t* data, size_t size);
+
+ bool IsOk() const;
+ DeviceTypes DeviceType() const;
+
+ // lv_sim: returned by Process(), public to be modified by main.cpp
+ uint32_t steps = 0;
+
+ private:
+ void Reset();
+
+ TwiMaster& twiMaster;
+ uint8_t deviceAddress = 0x18;
+ bma4_dev bma;
+ bool isOk = true;
+ bool isResetOk = false;
+ DeviceTypes deviceType = DeviceTypes::Unknown;
+ };
+ }
+}
\ No newline at end of file
diff --git a/sim/drivers/Cst816s.cpp b/sim/drivers/Cst816s.cpp
new file mode 100644
index 0000000..6fae3ba
--- /dev/null
+++ b/sim/drivers/Cst816s.cpp
@@ -0,0 +1,112 @@
+#include "drivers/Cst816s.h"
+#include
+#include
+#include
+
+using namespace Pinetime::Drivers;
+
+/* References :
+ * This implementation is based on this article :
+ * https://medium.com/@ly.lee/building-a-rust-driver-for-pinetimes-touch-controller-cbc1a5d5d3e9 Touch panel datasheet (weird chinese
+ * translation) : https://wiki.pine64.org/images/5/51/CST816S%E6%95%B0%E6%8D%AE%E6%89%8B%E5%86%8CV1.1.en.pdf
+ *
+ * TODO : we need a complete datasheet and protocol reference!
+ * */
+
+//Cst816S::Cst816S(TwiMaster& twiMaster, uint8_t twiAddress) : twiMaster {twiMaster}, twiAddress {twiAddress} {
+//}
+Cst816S::Cst816S() {
+}
+
+bool Cst816S::Init() {
+ return true;
+}
+
+Cst816S::TouchInfos Cst816S::GetTouchInfo() {
+ int x, y;
+ uint32_t buttons = SDL_GetMouseState(&x, &y);
+
+ Cst816S::TouchInfos info;
+ info.x = x;
+ info.y = y;
+ info.touching = (buttons & SDL_BUTTON_LMASK) != 0;
+ //info.gesture = gesture;
+ info.isValid = x > 0 && x <= maxX && y > 0 && y <= maxY;
+ if(info.isValid) {
+ if(!is_pressed && info.touching) {
+ // start klick
+ pressed_since = std::chrono::steady_clock::now();
+ is_pressed = true;
+ is_long_press = false;
+ is_swipe = false;
+ is_stationary = true;
+ x_start = info.x;
+ y_start = info.y;
+ } else if(is_pressed && info.touching) {
+ // is it long press?
+ if (is_stationary) { // check if while touching we moved away from the start coordinates
+ double x_diff = static_cast(info.x) - x_start;
+ double y_diff = static_cast(info.y) - y_start;
+ double norm = hypot(x_diff, y_diff);
+ if(norm > 20) { // we moved out of start area
+ is_stationary = false;
+ }
+ }
+ if (!is_long_press && !is_swipe) { // check for long-press only if it's not yet a long-press and didn't move
+ std::chrono::duration press_duration = std::chrono::steady_clock::now() - pressed_since;
+ if(is_stationary && press_duration.count() > 1.0) {
+ // longer than 1 second pressed, then it is long-press
+ is_long_press = true;
+ info.gesture = Gestures::LongPress;
+ } else if(!is_stationary) {
+ // moved mouse fast enough to not be a long-press
+ is_swipe = true;
+ double x_diff = static_cast(info.x) - x_start;
+ double y_diff = static_cast(info.y) - y_start;
+ if (fabs(x_diff) > fabs(y_diff)) {
+ // x-swipe
+ if (x_diff < 0) {
+ info.gesture = Gestures::SlideLeft;
+ } else {
+ info.gesture = Gestures::SlideRight;
+ }
+ } else {
+ // y-swipe
+ if (y_diff < 0) {
+ info.gesture = Gestures::SlideUp;
+ } else {
+ info.gesture = Gestures::SlideDown;
+ }
+ }
+ }
+ }
+ } else if(is_pressed && !info.touching) {
+ // end klick
+ is_pressed = false;
+
+ double x_diff = static_cast(info.x) - x_start;
+ double y_diff = static_cast(info.y) - y_start;
+ double norm = hypot(x_diff, y_diff);
+ if(norm < 20) {
+ if(is_stationary && !is_long_press && !is_swipe) {
+ // no swipe with less than 5 pixel mouse movement
+ info.gesture = Gestures::SingleTap;
+ }
+ }
+ }
+ }
+ return info;
+}
+
+void Cst816S::Sleep() {
+ NRF_LOG_INFO("[TOUCHPANEL] Sleep");
+}
+
+void Cst816S::Wakeup() {
+ Init();
+ NRF_LOG_INFO("[TOUCHPANEL] Wakeup");
+}
+
+bool Cst816S::CheckDeviceIds() {
+ return chipId == 0xb4 && vendorId == 0 && fwVersion == 1;
+}
diff --git a/sim/drivers/Cst816s.h b/sim/drivers/Cst816s.h
new file mode 100644
index 0000000..3a989da
--- /dev/null
+++ b/sim/drivers/Cst816s.h
@@ -0,0 +1,88 @@
+#pragma once
+
+//#include "drivers/TwiMaster.h"
+#include
+#include
+
+namespace Pinetime {
+ namespace Drivers {
+ class Cst816S {
+ public:
+ enum class Gestures : uint8_t {
+ None = 0x00,
+ SlideDown = 0x01,
+ SlideUp = 0x02,
+ SlideLeft = 0x03,
+ SlideRight = 0x04,
+ SingleTap = 0x05,
+ DoubleTap = 0x0B,
+ LongPress = 0x0C
+ };
+ struct TouchInfos {
+ uint16_t x = 0;
+ uint16_t y = 0;
+ Gestures gesture = Gestures::None;
+ bool touching = false;
+ bool isValid = false;
+ };
+
+ Cst816S();
+// Cst816S(TwiMaster& twiMaster, uint8_t twiAddress);
+ Cst816S(const Cst816S&) = delete;
+ Cst816S& operator=(const Cst816S&) = delete;
+ Cst816S(Cst816S&&) = delete;
+ Cst816S& operator=(Cst816S&&) = delete;
+
+ bool Init();
+ TouchInfos GetTouchInfo();
+ void Sleep();
+ void Wakeup();
+
+ uint8_t GetChipId() const {
+ return chipId;
+ }
+ uint8_t GetVendorId() const {
+ return vendorId;
+ }
+ uint8_t GetFwVersion() const {
+ return fwVersion;
+ }
+ private:
+ bool CheckDeviceIds();
+
+ // Unused/Unavailable commented out
+ static constexpr uint8_t gestureIndex = 1;
+ static constexpr uint8_t touchPointNumIndex = 2;
+ //static constexpr uint8_t touchEventIndex = 3;
+ static constexpr uint8_t touchXHighIndex = 3;
+ static constexpr uint8_t touchXLowIndex = 4;
+ //static constexpr uint8_t touchIdIndex = 5;
+ static constexpr uint8_t touchYHighIndex = 5;
+ static constexpr uint8_t touchYLowIndex = 6;
+ //static constexpr uint8_t touchStep = 6;
+ //static constexpr uint8_t touchXYIndex = 7;
+ //static constexpr uint8_t touchMiscIndex = 8;
+
+ static constexpr uint8_t maxX = 240;
+ static constexpr uint8_t maxY = 240;
+
+// TwiMaster& twiMaster;
+// uint8_t twiAddress;
+
+ const uint8_t chipId = 0xb4;
+ const uint8_t vendorId = 0;
+ const uint8_t fwVersion = 1;
+
+
+ // simulation members for swipe detection from mouse
+ std::chrono::time_point pressed_since;
+ bool is_pressed = false;
+ bool is_long_press = false;
+ bool is_stationary = true;
+ bool is_swipe = false;
+ uint8_t x_start;
+ uint8_t y_start;
+ };
+
+ }
+}
diff --git a/sim/drivers/Hrs3300.cpp b/sim/drivers/Hrs3300.cpp
new file mode 100644
index 0000000..4374dd4
--- /dev/null
+++ b/sim/drivers/Hrs3300.cpp
@@ -0,0 +1,105 @@
+/*
+ SPDX-License-Identifier: LGPL-3.0-or-later
+ Original work Copyright (C) 2020 Daniel Thompson
+ C++ port Copyright (C) 2021 Jean-François Milants
+*/
+
+#include "drivers/Hrs3300.h"
+#include
+#include