diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 81926a91..4a71b7e0 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,7 +1,7 @@
# GitHub Actions Workflow to build FreeRTOS Firmware for PineTime Smart Watch
# See https://lupyuen.github.io/pinetime-rust-mynewt/articles/cloud
-# Based on https://github.com/JF002/Pinetime/blob/master/doc/buildAndProgram.md
-# and https://github.com/JF002/Pinetime/blob/master/bootloader/README.md
+# Based on https://github.com/JF002/InfiniTime/blob/master/doc/buildAndProgram.md
+# and https://github.com/JF002/InfiniTime/blob/master/bootloader/README.md
# Name of this Workflow
name: Build PineTime Firmware
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 60a86f76..7bdfbcb1 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -8,30 +8,10 @@
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -47,8 +27,5 @@
-
-
-
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 2865af94..5e09a03c 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,19 +1,5 @@
{
- "cmake.configureArgs": [
- "-DARM_NONE_EABI_TOOLCHAIN_PATH=/home/panky92/embedded/gcc-arm-none-eabi-9-2020-q2-update",
- "-DNRF5_SDK_PATH=/home/panky92/embedded/nRF5_sdk",
- "-DUSE_OPENOCD=1"
- ],
- "cmake.buildTask": true,
"files.associations": {
- "streambuf": "cpp",
- "chrono": "cpp",
- "tuple": "cpp",
- "functional": "cpp",
- "*.tcc": "cpp",
- "string": "cpp",
- "fstream": "cpp",
- "iosfwd": "cpp",
- "nrf_rtc.h": "c"
+ "chrono": "cpp"
}
}
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ac7c8903..54ef9faf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.10)
-project(pinetime VERSION 0.14.0 LANGUAGES C CXX ASM)
+project(pinetime VERSION 0.14.1 LANGUAGES C CXX ASM)
set(NRF_TARGET "nrf52")
@@ -41,6 +41,10 @@ if(DEFINED USE_DEBUG_PINS AND USE_DEBUG_PINS)
add_definitions(-DUSE_DEBUG_PINS)
endif()
+if(BUILD_DFU)
+ set(BUILD_DFU true)
+endif()
+
message("BUILD CONFIGURATION")
message("-------------------")
message(" * Version : " ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH})
@@ -62,6 +66,11 @@ if(USE_DEBUG_PINS)
else()
message(" * Debug pins : Disabled")
endif()
+if(BUILD_DFU)
+ message(" * Build DFU (using adafruit-nrfutil) : Enabled")
+else()
+ message(" * Build DFU (using adafruit-nrfutil) : Disabled")
+endif()
set(VERSION_EDIT_WARNING "// Do not edit this file, it is automatically generated by CMAKE!")
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/Version.h.in ${CMAKE_CURRENT_SOURCE_DIR}/src/Version.h)
diff --git a/README.md b/README.md
index 3f89ec7d..c277b36c 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# PineTime
-![Build PineTime Firmware](https://github.com/JF002/Pinetime/workflows/Build%20PineTime%20Firmware/badge.svg?branch=master)
+![Build PineTime Firmware](https://github.com/JF002/InfiniTime/workflows/Build%20PineTime%20Firmware/badge.svg?branch=master)
> The PineTime is a free and open source smartwatch capable of running custom-built open operating systems. Some of the notable features include a heart rate monitor, a week-long battery as well as a capacitive touch IPS display that is legible in direct sunlight. It is a fully community driven side-project, which means that it will ultimately be up to the developers and end-users to determine when they deem the PineTime ready to ship.
diff --git a/doc/buildAndProgram.md b/doc/buildAndProgram.md
index 79ca519d..484916a8 100644
--- a/doc/buildAndProgram.md
+++ b/doc/buildAndProgram.md
@@ -8,8 +8,8 @@ To build this project, you'll need:
## Build steps
### Clone the repo
```
-git clone https://github.com/JF002/Pinetime.git
-cd Pinetime
+git clone https://github.com/JF002/InfiniTime.git
+cd InfiniTime
git submodule update --init
mkdir build
cd build
@@ -26,7 +26,10 @@ CMake configures the project according to variables you specify the command line
**NRFJPROG**|Path to the NRFJProg executable. Used only if `USE_JLINK` is 1.|`-DNRFJPROG=/opt/nrfjprog/nrfjprog`
**GDB_CLIENT_BIN_PATH**|Path to arm-none-eabi-gdb executable. Used only if `USE_GDB_CLIENT` is 1.|`-DGDB_CLIENT_BIN_PATH=/home/jf/nrf52/gcc-arm-none-eabi-9-2019-q4-major/bin/arm-none-eabi-gdb`
**GDB_CLIENT_TARGET_REMOTE**|Target remote connection string. Used only if `USE_GDB_CLIENT` is 1.|`-DGDB_CLIENT_TARGET_REMOTE=/dev/ttyACM0`
+**BUILD_DFU (\*)**|Build DFU files while building (needs [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil)).|`-BUILD_DFU=1`
+####(*) Note about **BUILD_DFU**:
+DFU files are the files you'll need to install your build of InfiniTime using OTA (over-the-air) mecanism. To generate the DFU file, the Python tool [adafruit-nrfutil](https://github.com/adafruit/Adafruit_nRF52_nrfutil) is needed on your system. Check that this tool is properly installed before enabling this option.
#### CMake command line for JLink
```
@@ -45,11 +48,14 @@ cmake -DARM_NONE_EABI_TOOLCHAIN_PATH=... -DNRF5_SDK_PATH=... -DUSE_OPENOCD=1 -DG
### Build the project
During the project generation, CMake created the following targets:
-- FLASH_ERASE : mass erase the flash memory of the NRF52.
-- FLASH_pinetime-app : flash the firmware into the NRF52.
-- pinetime-app : build the standalone (without bootloader support) version of the firmware.
-- pinetime-mcuboot-app : build the firmware with the support of the bootloader (based on MCUBoot).
-- pinetime-graphics : small firmware that writes the boot graphics into the SPI flash.
+- **FLASH_ERASE** : mass erase the flash memory of the NRF52.
+- **FLASH_pinetime-app** : flash the firmware into the NRF52.
+- **pinetime-app** : build the standalone (without bootloader support) version of the firmware.
+- **pinetime-recovery** : build the standalone recovery version of infinitime (light firmware that only supports OTA and basic UI)
+- **pinetime-recovery-loader** : build the standalone tool that flashes the recovery firmware into the external SPI flash
+- **pinetime-mcuboot-app** : build the firmware with the support of the bootloader (based on MCUBoot).
+- **pinetime-mcuboot-recovery** : build pinetime-recovery with bootloader support
+- **pinetime-mcuboot-recovery-loader** : build pinetime-recovery-loader with bootloader support
If you just want to build the project and run it on the Pinetime, using *pinetime-app* is recommanded. See [this page](../bootloader/README.md) for more info about bootloader support.
@@ -64,8 +70,11 @@ Binary files are generated into the folder `src`:
- **pinetime-app.map** : map file
- **pinetime-mcuboot-app.bin, .hex and .out** : firmware with bootloader support in bin, hex and out formats.
- **pinetime-mcuboot-app.map** : map file
- - **pinetime-graphics.bin, .hex and .out** : firmware for the boot graphic in bin, hex and out formats.
- - **pinetime-graphics.map** : map file
+ - **pinetime-mcuboot-app-image** : MCUBoot image of the firmware
+ - **pinetime-mcuboot-ap-dfu** : DFU file of the firmware
+
+The same files are generated for **pinetime-recovery** and **pinetime-recoveryloader**
+
### Program and run
#### Using CMake targets
diff --git a/doc/contribute.md b/doc/contribute.md
index 40441cd2..fe385125 100644
--- a/doc/contribute.md
+++ b/doc/contribute.md
@@ -1,6 +1,6 @@
# How to contribute?
## Report bugs
-You use your Pinetime and find a bug in the firmware? [Create an issue on Github](https://github.com/JF002/Pinetime/issues) explaining the bug, how to reproduce it, the version of the firmware you use...
+You use your Pinetime and find a bug in the firmware? [Create an issue on Github](https://github.com/JF002/InfiniTime/issues) explaining the bug, how to reproduce it, the version of the firmware you use...
## Write and improve documentation
Documentation might be incomplete, or not clear enough, and it is always possible to improve it with better wording, pictures, photo, video,...
@@ -41,4 +41,4 @@ The most important rule to follow is to try to keep the code as easy to read and
- **Include guard** : `#pragma once` (no `#ifdef __MODULE__ / #define __MODULE__ / #endif`)
- **Includes** :
- files from the project : `#include "relative/path/to/the/file.h"`
- - external files and std : `#include `
\ No newline at end of file
+ - external files and std : `#include `
diff --git a/doc/filesInReleaseNotes.md b/doc/filesInReleaseNotes.md
index 2fdfadf4..f48a0c10 100644
--- a/doc/filesInReleaseNotes.md
+++ b/doc/filesInReleaseNotes.md
@@ -1,9 +1,9 @@
# Using the releases
-For each new *stable* version of Pinetime, a [release note](https://github.com/JF002/Pinetime/releases) is created. It contains a description of the main changes in the release and some files you can use to flash the firmware in your Pinetime.
+For each new *stable* version of Pinetime, a [release note](https://github.com/JF002/InfiniTime/releases) is created. It contains a description of the main changes in the release and some files you can use to flash the firmware in your Pinetime.
This page describes the files from the release notes and how to use them.
-**NOTE :** the files included in different could be different. This page describes the release note of [version 0.7.1](https://github.com/JF002/Pinetime/releases/tag/0.7.1), which is the version that'll probably be pre-programmed at the factory for the next batch of Pinetime devkits.
+**NOTE :** the files included in different could be different. This page describes the release note of [version 0.7.1](https://github.com/JF002/InfiniTime/releases/tag/0.7.1), which is the version that'll probably be pre-programmed at the factory for the next batch of Pinetime devkits.
## Files included in the release note
diff --git a/doc/gettingStarted/gettingStarted.md b/doc/gettingStarted/gettingStarted.md
index a3aa49ab..123bf4ae 100644
--- a/doc/gettingStarted/gettingStarted.md
+++ b/doc/gettingStarted/gettingStarted.md
@@ -8,7 +8,7 @@ If you just want to flash or upgrade InfiniTime on your PineTime, this page is f
- [How to flash InfiniTime using the SWD interface](#how-to-flash-infinitime-using-the-swd-interface)
## InfiniTime releases and versions
-All releases of InfiniTime are available on the [release page of the GitHub repo](https://github.com/JF002/Pinetime/releases).
+All releases of InfiniTime are available on the [release page of the GitHub repo](https://github.com/JF002/InfiniTime/releases).
Versions that are tagged as **RELEASE CANDIDATE** are pre-release versions, that are available for testing before actually releasing a new stable version. If you want to help us debug the project and provide stable versions to other user, you can use them. If you want stable and tested version, you should not flash these release candidate version.
@@ -98,4 +98,4 @@ Launch NRFConnect, tap the sandwish button on the top left and select *Configure
Tap *Add service* and select the server configuration *Current Time service*. Tap OK and connect to your PineTime, it should automcatically sync the time once the connection is established!
-![NRFConnect CTS 1](nrfconnectcts1.jpg)
\ No newline at end of file
+![NRFConnect CTS 1](nrfconnectcts1.jpg)
diff --git a/docker/Dockerfile b/docker/Dockerfile
index 894e534b..a6caa24e 100644
--- a/docker/Dockerfile
+++ b/docker/Dockerfile
@@ -17,7 +17,8 @@ RUN apt-get update -qq \
# aarch64 packages
libffi-dev \
libssl-dev \
- python3-dev \
+ python3-dev \
+ python \
&& rm -rf /var/cache/apt/* /var/lib/apt/lists/*;
RUN pip3 install adafruit-nrfutil
diff --git a/docker/build.sh b/docker/build.sh
index 8f0d0fa9..2fa7d920 100755
--- a/docker/build.sh
+++ b/docker/build.sh
@@ -63,6 +63,7 @@ CmakeGenerate() {
-DUSE_OPENOCD=1 \
-DARM_NONE_EABI_TOOLCHAIN_PATH="$TOOLS_DIR/$GCC_ARM_VER" \
-DNRF5_SDK_PATH="$TOOLS_DIR/$NRF_SDK_VER" \
+ -DBUILD_DFU=1 \
"$SOURCES_DIR"
cmake -L -N .
}
diff --git a/docker/post_build.sh.in b/docker/post_build.sh.in
index 53ae343a..db6e7a94 100755
--- a/docker/post_build.sh.in
+++ b/docker/post_build.sh.in
@@ -9,15 +9,12 @@ export PROJECT_VERSION="@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT
mkdir -p "$OUTPUT_DIR"
cp "$SOURCES_DIR"/bootloader/bootloader-5.0.4.bin $OUTPUT_DIR/bootloader.bin
+cp "$BUILD_DIR/src/pinetime-mcuboot-app-image-$PROJECT_VERSION.bin" "$OUTPUT_DIR/pinetime-mcuboot-app-image-$PROJECT_VERSION.bin"
+cp "$BUILD_DIR/src/pinetime-mcuboot-app-dfu-$PROJECT_VERSION.zip" "$OUTPUT_DIR/pinetime-mcuboot-app-dfu-$PROJECT_VERSION.zip"
-"$TOOLS_DIR"/mcuboot/scripts/imgtool.py create --version 1.0.0 \
- --align 4 --header-size 32 --slot-size 475136 --pad-header \
- "$BUILD_DIR/src/pinetime-mcuboot-app-$PROJECT_VERSION.bin" \
- "$OUTPUT_DIR/image-$PROJECT_VERSION.bin"
+cp "$BUILD_DIR/src/pinetime-mcuboot-recovery-loader-image-$PROJECT_VERSION.bin" "$OUTPUT_DIR/pinetime-mcuboot-recovery-loader-image-$PROJECT_VERSION.bin"
+cp "$BUILD_DIR/src/pinetime-mcuboot-recovery-loader-dfu-$PROJECT_VERSION.zip" "$OUTPUT_DIR/pinetime-mcuboot-recovery-loader-dfu-$PROJECT_VERSION.zip"
-adafruit-nrfutil dfu genpkg --dev-type 0x0052 \
- --application "$OUTPUT_DIR/image-$PROJECT_VERSION.bin" \
- "$OUTPUT_DIR/dfu-$PROJECT_VERSION.zip"
mkdir -p "$OUTPUT_DIR/src"
cd "$BUILD_DIR"
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 08e9d631..59dc9730 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -500,6 +500,15 @@ list(APPEND SOURCE_FILES
displayapp/screens/Notifications.cpp
displayapp/screens/Twos.cpp
displayapp/screens/HeartRate.cpp
+
+ ## Watch faces
+ displayapp/icons/bg_clock.c
+ displayapp/screens/WatchFaceAnalog.cpp
+
+ displayapp/screens/WatchFaceDigital.cpp
+
+ ##
+
main.cpp
drivers/St7789.cpp
drivers/SpiNorFlash.cpp
@@ -529,6 +538,7 @@ list(APPEND SOURCE_FILES
components/ble/HeartRateService.cpp
components/firmwarevalidator/FirmwareValidator.cpp
components/motor/MotorController.cpp
+ components/settings/Settings.cpp
drivers/Cst816s.cpp
FreeRTOS/port.c
FreeRTOS/port_cmsis_systick.c
@@ -549,7 +559,59 @@ list(APPEND SOURCE_FILES
components/heartrate/HeartRateController.cpp
)
-list(APPEND GRAPHICS_SOURCE_FILES
+list(APPEND RECOVERY_SOURCE_FILES
+ BootloaderVersion.cpp
+ logging/NrfLogger.cpp
+ displayapp/DisplayAppRecovery.cpp
+
+ main.cpp
+ drivers/St7789.cpp
+ drivers/SpiNorFlash.cpp
+ drivers/SpiMaster.cpp
+ drivers/Spi.cpp
+ drivers/Watchdog.cpp
+ drivers/DebugPins.cpp
+ drivers/InternalFlash.cpp
+ drivers/Hrs3300.cpp
+ components/battery/BatteryController.cpp
+ components/ble/BleController.cpp
+ components/ble/NotificationManager.cpp
+ components/datetime/DateTimeController.cpp
+ components/brightness/BrightnessController.cpp
+ components/ble/NimbleController.cpp
+ components/ble/DeviceInformationService.cpp
+ components/ble/CurrentTimeClient.cpp
+ components/ble/AlertNotificationClient.cpp
+ components/ble/DfuService.cpp
+ components/ble/CurrentTimeService.cpp
+ components/ble/AlertNotificationService.cpp
+ components/ble/MusicService.cpp
+ components/ble/BatteryInformationService.cpp
+ components/ble/ImmediateAlertService.cpp
+ components/ble/ServiceDiscovery.cpp
+ components/ble/NavigationService.cpp
+ components/ble/HeartRateService.cpp
+ components/firmwarevalidator/FirmwareValidator.cpp
+ components/settings/Settings.cpp
+ drivers/Cst816s.cpp
+ FreeRTOS/port.c
+ FreeRTOS/port_cmsis_systick.c
+ FreeRTOS/port_cmsis.c
+
+ systemtask/SystemTask.cpp
+ drivers/TwiMaster.cpp
+ components/gfx/Gfx.cpp
+ displayapp/icons/infinitime/infinitime-nb.c
+ components/rle/RleDecoder.cpp
+ components/heartrate/HeartRateController.cpp
+ heartratetask/HeartRateTask.cpp
+ components/heartrate/Ppg.cpp
+ components/heartrate/Biquad.cpp
+ components/heartrate/Ptagc.cpp
+ components/motor/MotorController.cpp
+ )
+
+list(APPEND RECOVERYLOADER_SOURCE_FILES
# FreeRTOS
FreeRTOS/port.c
FreeRTOS/port_cmsis_systick.c
@@ -560,18 +622,23 @@ list(APPEND GRAPHICS_SOURCE_FILES
drivers/Spi.cpp
logging/NrfLogger.cpp
+ components/rle/RleDecoder.cpp
+
components/gfx/Gfx.cpp
drivers/St7789.cpp
components/brightness/BrightnessController.cpp
- graphics.cpp
+ displayapp/icons/infinitime/infinitime-nb.c
+ recoveryLoader.cpp
)
+
set(INCLUDE_FILES
BootloaderVersion.h
logging/Logger.h
logging/NrfLogger.h
displayapp/DisplayApp.h
+ displayapp/Messages.h
displayapp/TouchEvents.h
displayapp/screens/Screen.h
displayapp/screens/Clock.h
@@ -617,7 +684,8 @@ set(INCLUDE_FILES
components/ble/ImmediateAlertService.h
components/ble/ServiceDiscovery.h
components/ble/BleClient.h
- components/ble/HeartRateService.h.h
+ components/ble/HeartRateService.h
+ components/settings/Settings.h
drivers/Cst816s.h
FreeRTOS/portmacro.h
FreeRTOS/portmacro_cmsis.h
@@ -822,8 +890,8 @@ add_custom_command(TARGET ${EXECUTABLE_NAME}
# Build binary intended to be used by bootloader
set(EXECUTABLE_MCUBOOT_NAME "pinetime-mcuboot-app")
set(EXECUTABLE_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
-set(IMAGE_MCUBOOT_FILE_NAME image-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.bin)
-set(DFU_FILE_NAME dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
+set(IMAGE_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-image-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.bin)
+set(DFU_MCUBOOT_FILE_NAME ${EXECUTABLE_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
set(NRF5_LINKER_SCRIPT_MCUBOOT "${CMAKE_SOURCE_DIR}/gcc_nrf52-mcuboot.ld")
add_executable(${EXECUTABLE_MCUBOOT_NAME} ${SOURCE_FILES})
target_link_libraries(${EXECUTABLE_MCUBOOT_NAME} nimble nrf-sdk lvgl)
@@ -848,16 +916,26 @@ add_custom_command(TARGET ${EXECUTABLE_MCUBOOT_NAME}
COMMAND ${CMAKE_SIZE_UTIL} ${EXECUTABLE_MCUBOOT_FILE_NAME}.out
COMMAND ${CMAKE_OBJCOPY} -O binary ${EXECUTABLE_MCUBOOT_FILE_NAME}.out "${EXECUTABLE_MCUBOOT_FILE_NAME}.bin"
COMMAND ${CMAKE_OBJCOPY} -O ihex ${EXECUTABLE_MCUBOOT_FILE_NAME}.out "${EXECUTABLE_MCUBOOT_FILE_NAME}.hex"
+ COMMAND ${CMAKE_SOURCE_DIR}/tools/mcuboot/imgtool.py create --align 4 --version 1.0.0 --header-size 32 --slot-size 475136 --pad-header ${EXECUTABLE_MCUBOOT_FILE_NAME}.bin ${IMAGE_MCUBOOT_FILE_NAME}
COMMENT "post build steps for ${EXECUTABLE_MCUBOOT_FILE_NAME}"
)
-# Build binary that writes the graphic assets for the bootloader
-set(EXECUTABLE_GRAPHICS_NAME "pinetime-graphics")
-set(EXECUTABLE_GRAPHICS_FILE_NAME ${EXECUTABLE_GRAPHICS_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
-add_executable(${EXECUTABLE_GRAPHICS_NAME} ${GRAPHICS_SOURCE_FILES})
-target_link_libraries(${EXECUTABLE_GRAPHICS_NAME} nrf-sdk)
-set_target_properties(${EXECUTABLE_GRAPHICS_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_GRAPHICS_FILE_NAME})
-target_compile_options(${EXECUTABLE_GRAPHICS_NAME} PUBLIC
+if(BUILD_DFU)
+ add_custom_command(TARGET ${EXECUTABLE_MCUBOOT_NAME}
+ POST_BUILD
+ COMMAND adafruit-nrfutil dfu genpkg --dev-type 0x0052 --application ${IMAGE_MCUBOOT_FILE_NAME} ${DFU_MCUBOOT_FILE_NAME}
+ COMMENT "post build (DFU) steps for ${EXECUTABLE_MCUBOOT_FILE_NAME}"
+ )
+endif()
+
+# InfiniTime recovery firmware (autonomous)
+set(EXECUTABLE_RECOVERY_NAME "pinetime-recovery")
+set(EXECUTABLE_RECOVERY_FILE_NAME ${EXECUTABLE_RECOVERY_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
+add_executable(${EXECUTABLE_RECOVERY_NAME} ${RECOVERY_SOURCE_FILES})
+target_link_libraries(${EXECUTABLE_RECOVERY_NAME} nimble nrf-sdk)
+set_target_properties(${EXECUTABLE_RECOVERY_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_FILE_NAME})
+target_compile_definitions(${EXECUTABLE_RECOVERY_NAME} PUBLIC "PINETIME_IS_RECOVERY")
+target_compile_options(${EXECUTABLE_RECOVERY_NAME} PUBLIC
$<$,$>: ${COMMON_FLAGS} -O0 -g3>
$<$,$>: ${COMMON_FLAGS} -O3>
$<$,$>: ${COMMON_FLAGS} -O0 -g3>
@@ -865,21 +943,142 @@ target_compile_options(${EXECUTABLE_GRAPHICS_NAME} PUBLIC
$<$: -MP -MD -x assembler-with-cpp>
)
-set_target_properties(${EXECUTABLE_GRAPHICS_NAME} PROPERTIES
+set_target_properties(${EXECUTABLE_RECOVERY_NAME} PROPERTIES
SUFFIX ".out"
- LINK_FLAGS "-mthumb -mabi=aapcs -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_GRAPHICS_FILE_NAME}.map"
+ LINK_FLAGS "-mthumb -mabi=aapcs -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_RECOVERY_FILE_NAME}.map"
CXX_STANDARD 11
C_STANDARD 99
)
-add_custom_command(TARGET ${EXECUTABLE_GRAPHICS_NAME}
+add_custom_command(TARGET ${EXECUTABLE_RECOVERY_NAME}
POST_BUILD
- COMMAND ${CMAKE_SIZE_UTIL} ${EXECUTABLE_GRAPHICS_FILE_NAME}.out
- COMMAND ${CMAKE_OBJCOPY} -O binary ${EXECUTABLE_GRAPHICS_FILE_NAME}.out "${EXECUTABLE_GRAPHICS_FILE_NAME}.bin"
- COMMAND ${CMAKE_OBJCOPY} -O ihex ${EXECUTABLE_GRAPHICS_FILE_NAME}.out "${EXECUTABLE_GRAPHICS_FILE_NAME}.hex"
- COMMENT "post build steps for ${EXECUTABLE_GRAPHICS_FILE_NAME}"
+ COMMAND ${CMAKE_SIZE_UTIL} ${EXECUTABLE_RECOVERY_FILE_NAME}.out
+ COMMAND ${CMAKE_OBJCOPY} -O binary ${EXECUTABLE_RECOVERY_FILE_NAME}.out "${EXECUTABLE_RECOVERY_FILE_NAME}.bin"
+ COMMAND ${CMAKE_OBJCOPY} -O ihex ${EXECUTABLE_RECOVERY_FILE_NAME}.out "${EXECUTABLE_RECOVERY_FILE_NAME}.hex"
+ COMMENT "post build steps for ${EXECUTABLE_RECOVERY_FILE_NAME}"
)
+# InfiniTime recovery firmware (mcuboot)
+set(EXECUTABLE_RECOVERY_MCUBOOT_NAME "pinetime-mcuboot-recovery")
+set(EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
+set(IMAGE_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-image-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.bin)
+set(DFU_RECOVERY_MCUBOOT_FILE_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
+add_executable(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} ${RECOVERY_SOURCE_FILES})
+target_link_libraries(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} nimble nrf-sdk)
+set_target_properties(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME})
+target_compile_definitions(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC "PINETIME_IS_RECOVERY")
+target_compile_options(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PUBLIC
+ $<$,$>: ${COMMON_FLAGS} -O0 -g3>
+ $<$,$>: ${COMMON_FLAGS} -O3>
+ $<$,$>: ${COMMON_FLAGS} -O0 -g3>
+ $<$,$>: ${COMMON_FLAGS} -O3>
+ $<$: -MP -MD -x assembler-with-cpp>
+ )
+
+set_target_properties(${EXECUTABLE_RECOVERY_MCUBOOT_NAME} PROPERTIES
+ SUFFIX ".out"
+ LINK_FLAGS "-mthumb -mabi=aapcs -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT_MCUBOOT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME}.map"
+ CXX_STANDARD 11
+ C_STANDARD 99
+ )
+
+add_custom_command(TARGET ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}
+ POST_BUILD
+ COMMAND ${CMAKE_SIZE_UTIL} ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME}.out
+ COMMAND ${CMAKE_OBJCOPY} -O binary ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME}.out "${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME}.bin"
+ COMMAND ${CMAKE_OBJCOPY} -O ihex ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME}.out "${EXECUTABLE_RECOVERYY_MCUBOOT_FILE_NAME}.hex"
+ COMMAND ${CMAKE_SOURCE_DIR}/tools/mcuboot/imgtool.py create --align 4 --version 1.0.0 --header-size 32 --slot-size 475136 --pad-header ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME}.bin ${IMAGE_RECOVERY_MCUBOOT_FILE_NAME}
+ COMMAND python ${CMAKE_SOURCE_DIR}/tools/bin2c.py ${IMAGE_RECOVERY_MCUBOOT_FILE_NAME} recoveryImage > recoveryImage.h
+ COMMENT "post build steps for ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME}"
+ )
+
+if(BUILD_DFU)
+ add_custom_command(TARGET ${EXECUTABLE_RECOVERY_MCUBOOT_NAME}
+ POST_BUILD
+ COMMAND adafruit-nrfutil dfu genpkg --dev-type 0x0052 --application ${IMAGE_RECOVERY_MCUBOOT_FILE_NAME} ${DFU_RECOVERY_MCUBOOT_FILE_NAME}
+ COMMENT "post build (DFU) steps for ${EXECUTABLE_RECOVERY_MCUBOOT_FILE_NAME}"
+ )
+endif()
+
+# Build binary that writes the recovery image into the SPI flash memory
+set(EXECUTABLE_RECOVERYLOADER_NAME "pinetime-recovery-loader")
+set(EXECUTABLE_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_RECOVERYLOADER_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
+add_executable(${EXECUTABLE_RECOVERYLOADER_NAME} ${RECOVERYLOADER_SOURCE_FILES})
+target_link_libraries(${EXECUTABLE_RECOVERYLOADER_NAME} nrf-sdk)
+set_target_properties(${EXECUTABLE_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_RECOVERYLOADER_FILE_NAME})
+target_compile_options(${EXECUTABLE_RECOVERYLOADER_NAME} PUBLIC
+ $<$,$>: ${COMMON_FLAGS} -O0 -g3>
+ $<$,$>: ${COMMON_FLAGS} -O3>
+ $<$,$>: ${COMMON_FLAGS} -O0 -g3>
+ $<$,$>: ${COMMON_FLAGS} -O3>
+ $<$: -MP -MD -x assembler-with-cpp>
+ )
+target_include_directories(${EXECUTABLE_RECOVERYLOADER_NAME} PUBLIC
+ $
+ )
+add_dependencies(${EXECUTABLE_RECOVERYLOADER_NAME} ${EXECUTABLE_RECOVERY_MCUBOOT_NAME})
+
+set_target_properties(${EXECUTABLE_RECOVERYLOADER_NAME} PROPERTIES
+ SUFFIX ".out"
+ LINK_FLAGS "-mthumb -mabi=aapcs -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_RECOVERYLOADER_FILE_NAME}.map"
+ CXX_STANDARD 11
+ C_STANDARD 99
+ )
+
+add_custom_command(TARGET ${EXECUTABLE_RECOVERYLOADER_NAME}
+ POST_BUILD
+ COMMAND ${CMAKE_SIZE_UTIL} ${EXECUTABLE_RECOVERYLOADER_FILE_NAME}.out
+ COMMAND ${CMAKE_OBJCOPY} -O binary ${EXECUTABLE_RECOVERYLOADER_FILE_NAME}.out "${EXECUTABLE_RECOVERYLOADER_FILE_NAME}.bin"
+ COMMAND ${CMAKE_OBJCOPY} -O ihex ${EXECUTABLE_RECOVERYLOADER_FILE_NAME}.out "${EXECUTABLE_RECOVERYLOADER_FILE_NAME}.hex"
+ COMMENT "post build steps for ${EXECUTABLE_RECOVERYLOADER_FILE_NAME}"
+ )
+
+# Build binary that writes the recovery image (MCUBoot version)
+set(EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME "pinetime-mcuboot-recovery-loader")
+set(EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME}-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH})
+set(IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME}-image-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.bin)
+set(DFU_MCUBOOT_RECOVERYLOADER_FILE_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME}-dfu-${pinetime_VERSION_MAJOR}.${pinetime_VERSION_MINOR}.${pinetime_VERSION_PATCH}.zip)
+add_executable(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} ${RECOVERYLOADER_SOURCE_FILES})
+target_link_libraries(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} nrf-sdk)
+set_target_properties(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PROPERTIES OUTPUT_NAME ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME})
+target_compile_options(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PUBLIC
+ $<$,$>: ${COMMON_FLAGS} -O0 -g3>
+ $<$,$>: ${COMMON_FLAGS} -O3>
+ $<$,$>: ${COMMON_FLAGS} -O0 -g3>
+ $<$,$>: ${COMMON_FLAGS} -O3>
+ $<$: -MP -MD -x assembler-with-cpp>
+ )
+target_include_directories(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PUBLIC
+ $
+ )
+add_dependencies(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} ${EXECUTABLE_RECOVERY_MCUBOOT_NAME})
+
+set_target_properties(${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME} PROPERTIES
+ SUFFIX ".out"
+ LINK_FLAGS "-mthumb -mabi=aapcs -std=gnu++98 -std=c99 -L ${NRF5_SDK_PATH}/modules/nrfx/mdk -T${NRF5_LINKER_SCRIPT_MCUBOOT} -mcpu=cortex-m4 -mfloat-abi=hard -mfpu=fpv4-sp-d16 -Wl,--gc-sections --specs=nano.specs -lc -lnosys -lm -Wl,-Map=${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME}.map"
+ CXX_STANDARD 11
+ C_STANDARD 99
+ )
+
+add_custom_command(TARGET ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME}
+ POST_BUILD
+ COMMAND ${CMAKE_SIZE_UTIL} ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME}.out
+ COMMAND ${CMAKE_OBJCOPY} -O binary ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME}.out "${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME}.bin"
+ COMMAND ${CMAKE_OBJCOPY} -O ihex ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME}.out "${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME}.hex"
+ COMMAND ${CMAKE_SOURCE_DIR}/tools/mcuboot/imgtool.py create --align 4 --version 1.0.0 --header-size 32 --slot-size 475136 --pad-header ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME}.bin ${IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME}
+ COMMAND python ${CMAKE_SOURCE_DIR}/tools/bin2c.py ${IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME} recoveryLoaderImage > recoveryLoaderImage.h
+ COMMENT "post build steps for ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME}"
+ )
+
+if(BUILD_DFU)
+ add_custom_command(TARGET ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_NAME}
+ POST_BUILD
+ COMMAND adafruit-nrfutil dfu genpkg --dev-type 0x0052 --application ${IMAGE_MCUBOOT_RECOVERYLOADER_FILE_NAME} ${DFU_MCUBOOT_RECOVERYLOADER_FILE_NAME}
+ COMMENT "post build (DFU) steps for ${EXECUTABLE_MCUBOOT_RECOVERYLOADER_FILE_NAME}"
+ )
+endif()
+
+
# FLASH
if (USE_JLINK)
add_custom_target(FLASH_ERASE
diff --git a/src/components/datetime/DateTimeController.cpp b/src/components/datetime/DateTimeController.cpp
index 30d9c13f..59982477 100644
--- a/src/components/datetime/DateTimeController.cpp
+++ b/src/components/datetime/DateTimeController.cpp
@@ -64,3 +64,123 @@ void DateTime::UpdateTime(uint32_t systickCounter) {
second = time.seconds().count();
}
+const char *DateTime::MonthShortToString() {
+ return DateTime::MonthsString[(uint8_t)month];
+}
+
+const char *DateTime::MonthShortToStringLow() {
+ return DateTime::MonthsStringLow[(uint8_t)month];
+}
+
+const char *DateTime::MonthsToStringLow() {
+ return DateTime::MonthsLow[(uint8_t)month];
+}
+
+const char *DateTime::DayOfWeekToString() {
+ return DateTime::DaysString[(uint8_t)dayOfWeek];
+}
+
+const char *DateTime::DayOfWeekShortToString() {
+ return DateTime::DaysStringShort[(uint8_t)dayOfWeek];
+}
+
+const char *DateTime::DayOfWeekToStringLow() {
+ return DateTime::DaysStringLow[(uint8_t)dayOfWeek];
+}
+
+const char *DateTime::DayOfWeekShortToStringLow() {
+ return DateTime::DaysStringShortLow[(uint8_t)dayOfWeek];
+}
+
+
+char const *DateTime::DaysStringLow[] = {
+ "--",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday"
+};
+
+char const *DateTime::DaysStringShortLow[] = {
+ "--",
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+ "Sun"
+};
+
+char const *DateTime::DaysStringShort[] = {
+ "--",
+ "MON",
+ "TUE",
+ "WED",
+ "THU",
+ "FRI",
+ "SAT",
+ "SUN"
+};
+
+char const *DateTime::DaysString[] = {
+ "--",
+ "MONDAY",
+ "TUESDAY",
+ "WEDNESDAY",
+ "THURSDAY",
+ "FRIDAY",
+ "SATURDAY",
+ "SUNDAY"
+};
+
+char const *DateTime::MonthsString[] = {
+ "--",
+ "JAN",
+ "FEB",
+ "MAR",
+ "APR",
+ "MAY",
+ "JUN",
+ "JUL",
+ "AUG",
+ "SEP",
+ "OCT",
+ "NOV",
+ "DEC"
+};
+
+char const *DateTime::MonthsStringLow[] = {
+ "--",
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec"
+};
+
+char const *DateTime::MonthsLow[] = {
+ "--",
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December"
+};
\ No newline at end of file
diff --git a/src/components/datetime/DateTimeController.h b/src/components/datetime/DateTimeController.h
index d6020745..16bb59c9 100644
--- a/src/components/datetime/DateTimeController.h
+++ b/src/components/datetime/DateTimeController.h
@@ -20,6 +20,14 @@ namespace Pinetime {
uint8_t Minutes() const { return minute; }
uint8_t Seconds() const { return second; }
+ const char *MonthShortToString();
+ const char *MonthShortToStringLow();
+ const char *MonthsToStringLow();
+ const char *DayOfWeekToString();
+ const char *DayOfWeekShortToString();
+ const char *DayOfWeekToStringLow();
+ const char *DayOfWeekShortToStringLow();
+
std::chrono::time_point CurrentDateTime() const { return currentDateTime; }
std::chrono::seconds Uptime() const { return uptime; }
private:
@@ -34,6 +42,15 @@ namespace Pinetime {
uint32_t previousSystickCounter = 0;
std::chrono::time_point currentDateTime;
std::chrono::seconds uptime {0};
+
+ static char const *DaysString[];
+ static char const *DaysStringShort[];
+ static char const *DaysStringLow[];
+ static char const *DaysStringShortLow[];
+ static char const *MonthsString[];
+ static char const *MonthsStringLow[];
+ static char const *MonthsLow[];
+
};
}
}
\ No newline at end of file
diff --git a/src/components/gfx/Gfx.cpp b/src/components/gfx/Gfx.cpp
index 59c1da9b..59fa8164 100644
--- a/src/components/gfx/Gfx.cpp
+++ b/src/components/gfx/Gfx.cpp
@@ -17,9 +17,8 @@ void Gfx::ClearScreen() {
state.busy = true;
state.action = Action::FillRectangle;
state.taskToNotify = xTaskGetCurrentTaskHandle();
-
- lcd.BeginDrawBuffer(0, 0, width, height);
- lcd.NextDrawBuffer(reinterpret_cast(buffer), width * 2);
+
+ lcd.DrawBuffer(0, 0, width, height, reinterpret_cast(buffer), width * 2);
WaitTransferFinished();
}
@@ -34,8 +33,7 @@ void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint16_t col
state.color = color;
state.taskToNotify = xTaskGetCurrentTaskHandle();
- lcd.BeginDrawBuffer(x, y, w, h);
- lcd.NextDrawBuffer(reinterpret_cast(buffer), width * 2);
+ lcd.DrawBuffer(x, y, w, h, reinterpret_cast(buffer), width * 2);
WaitTransferFinished();
}
@@ -48,8 +46,7 @@ void Gfx::FillRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t* b)
state.color = 0x00;
state.taskToNotify = xTaskGetCurrentTaskHandle();
- lcd.BeginDrawBuffer(x, y, w, h);
- lcd.NextDrawBuffer(reinterpret_cast(b), width * 2);
+ lcd.DrawBuffer(x, y, w, h, reinterpret_cast(b), width * 2);
WaitTransferFinished();
}
@@ -120,8 +117,7 @@ void Gfx::DrawChar(const FONT_INFO *font, uint8_t c, uint8_t *x, uint8_t y, uint
state.color = color;
state.taskToNotify = xTaskGetCurrentTaskHandle();
- lcd.BeginDrawBuffer(*x, y, bytes_in_line*8, font->height);
- lcd.NextDrawBuffer(reinterpret_cast(&buffer), bytes_in_line*8*2);
+ lcd.DrawBuffer(*x, y, bytes_in_line*8, font->height, reinterpret_cast(&buffer), bytes_in_line*8*2);
WaitTransferFinished();
*x += font->charInfo[char_idx].widthBits + font->spacePixels;
diff --git a/src/components/rle/RleDecoder.cpp b/src/components/rle/RleDecoder.cpp
new file mode 100644
index 00000000..19a90fda
--- /dev/null
+++ b/src/components/rle/RleDecoder.cpp
@@ -0,0 +1,39 @@
+#include "RleDecoder.h"
+
+using namespace Pinetime::Tools;
+
+RleDecoder::RleDecoder(const uint8_t *buffer, size_t size) : buffer{buffer}, size{size} {
+
+}
+
+RleDecoder::RleDecoder(const uint8_t *buffer, size_t size, uint16_t foregroundColor, uint16_t backgroundColor) : RleDecoder{buffer, size} {
+ this->foregroundColor = foregroundColor;
+ this->backgroundColor = backgroundColor;
+}
+
+
+void RleDecoder::DecodeNext(uint8_t *output, size_t maxBytes) {
+ for (;encodedBufferIndex> 8;
+ output[bp + 1] = color & 0xff;
+ bp += 2;
+ rl -= 1;
+ processedCount++;
+
+ if (bp >= maxBytes) {
+ bp = 0;
+ y += 1;
+ return;
+ }
+ }
+ processedCount = 0;
+
+ if (color == backgroundColor)
+ color = foregroundColor;
+ else
+ color = backgroundColor;
+ }
+}
+
diff --git a/src/components/rle/RleDecoder.h b/src/components/rle/RleDecoder.h
new file mode 100644
index 00000000..0f607fb8
--- /dev/null
+++ b/src/components/rle/RleDecoder.h
@@ -0,0 +1,33 @@
+#pragma once
+
+#include
+#include
+
+namespace Pinetime {
+ namespace Tools {
+ /* 1-bit RLE decoder. Provide the encoded buffer to the constructor and then call DecodeNext() by
+ * specifying the output (decoded) buffer and the maximum number of bytes this buffer can handle.
+ *
+ * Code from https://github.com/daniel-thompson/wasp-bootloader by Daniel Thompson released under the MIT license.
+ */
+ class RleDecoder {
+ public:
+ RleDecoder(const uint8_t* buffer, size_t size);
+ RleDecoder(const uint8_t* buffer, size_t size, uint16_t foregroundColor, uint16_t backgroundColor);
+
+ void DecodeNext(uint8_t* output, size_t maxBytes);
+
+ private:
+ const uint8_t* buffer;
+ size_t size;
+
+ int encodedBufferIndex = 0;
+ int y = 0;
+ uint16_t bp = 0;
+ uint16_t foregroundColor = 0xffff;
+ uint16_t backgroundColor = 0;
+ uint16_t color = backgroundColor;
+ int processedCount = 0;
+ };
+ }
+}
diff --git a/src/components/settings/Settings.cpp b/src/components/settings/Settings.cpp
new file mode 100644
index 00000000..0c6cf610
--- /dev/null
+++ b/src/components/settings/Settings.cpp
@@ -0,0 +1,16 @@
+#include "Settings.h"
+
+using namespace Pinetime::Controllers;
+
+
+// TODO (team):
+// Read and write the settings to Flash
+//
+void Settings::Init() {
+ // default Clock face
+ clockFace = 0;
+
+ clockType = ClockType::H24;
+
+}
+
diff --git a/src/components/settings/Settings.h b/src/components/settings/Settings.h
new file mode 100644
index 00000000..fa67f35e
--- /dev/null
+++ b/src/components/settings/Settings.h
@@ -0,0 +1,30 @@
+#pragma once
+#include
+
+namespace Pinetime {
+ namespace Controllers {
+ class Settings {
+ public:
+ enum class ClockType {H24, H12};
+
+ void Init();
+
+ void SetClockFace( uint8_t face ) { clockFace = face; };
+ uint8_t GetClockFace() { return clockFace; };
+
+ void SetAppMenu( uint8_t menu ) { appMenu = menu; };
+ uint8_t GetAppMenu() { return appMenu; };
+
+ void SetClockType( ClockType clocktype ) { clockType = clocktype; };
+ ClockType GetClockType() { return clockType; };
+
+
+ private:
+ uint8_t clockFace = 0;
+ uint8_t appMenu = 0;
+
+ ClockType clockType = ClockType::H24;
+
+ };
+ }
+}
\ No newline at end of file
diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp
index 7fa2d127..d29864e8 100644
--- a/src/displayapp/DisplayApp.cpp
+++ b/src/displayapp/DisplayApp.cpp
@@ -26,13 +26,15 @@
#include "systemtask/SystemTask.h"
using namespace Pinetime::Applications;
+using namespace Pinetime::Applications::Display;
DisplayApp::DisplayApp(Drivers::St7789 &lcd, Components::LittleVgl &lvgl, Drivers::Cst816S &touchPanel,
Controllers::Battery &batteryController, Controllers::Ble &bleController,
Controllers::DateTime &dateTimeController, Drivers::WatchdogView &watchdog,
System::SystemTask &systemTask,
Pinetime::Controllers::NotificationManager& notificationManager,
- Pinetime::Controllers::HeartRateController& heartRateController) :
+ Pinetime::Controllers::HeartRateController& heartRateController,
+ Controllers::Settings &settingsController) :
lcd{lcd},
lvgl{lvgl},
batteryController{batteryController},
@@ -40,10 +42,11 @@ DisplayApp::DisplayApp(Drivers::St7789 &lcd, Components::LittleVgl &lvgl, Driver
dateTimeController{dateTimeController},
watchdog{watchdog},
touchPanel{touchPanel},
- currentScreen{new Screens::Clock(this, dateTimeController, batteryController, bleController, notificationManager, heartRateController) },
+ currentScreen{new Screens::Clock(this, dateTimeController, batteryController, bleController, notificationManager, settingsController, heartRateController) },
systemTask{systemTask},
notificationManager{notificationManager},
- heartRateController{heartRateController} {
+ heartRateController{heartRateController},
+ settingsController{settingsController} {
msgQueue = xQueueCreate(queueSize, itemSize);
onClockApp = true;
}
@@ -195,9 +198,9 @@ void DisplayApp::RunningState() {
onClockApp = false;
switch(nextApp) {
case Apps::None:
- case Apps::Launcher: currentScreen.reset(new Screens::ApplicationList(this)); break;
+ case Apps::Launcher: currentScreen.reset(new Screens::ApplicationList(this, settingsController)); break;
case Apps::Clock:
- currentScreen.reset(new Screens::Clock(this, dateTimeController, batteryController, bleController, notificationManager, heartRateController));
+ currentScreen.reset(new Screens::Clock(this, dateTimeController, batteryController, bleController, notificationManager, settingsController, heartRateController));
onClockApp = true;
break;
case Apps::SysInfo: currentScreen.reset(new Screens::SystemInfo(this, dateTimeController, batteryController, brightnessController, bleController, watchdog)); break;
@@ -222,7 +225,7 @@ void DisplayApp::IdleState() {
}
-void DisplayApp::PushMessage(DisplayApp::Messages msg) {
+void DisplayApp::PushMessage(Messages msg) {
BaseType_t xHigherPriorityTaskWoken;
xHigherPriorityTaskWoken = pdFALSE;
xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
diff --git a/src/displayapp/DisplayApp.h b/src/displayapp/DisplayApp.h
index c38404ba..c22aa1f2 100644
--- a/src/displayapp/DisplayApp.h
+++ b/src/displayapp/DisplayApp.h
@@ -9,7 +9,9 @@
#include "TouchEvents.h"
#include "components/brightness/BrightnessController.h"
#include "components/firmwarevalidator/FirmwareValidator.h"
+#include "components/settings/Settings.h"
#include "displayapp/screens/Screen.h"
+#include "Messages.h"
namespace Pinetime {
@@ -19,6 +21,7 @@ namespace Pinetime {
class WatchdogView;
}
namespace Controllers {
+ class Settings;
class Battery;
class Ble;
class DateTime;
@@ -33,9 +36,6 @@ namespace Pinetime {
class DisplayApp {
public:
enum class States {Idle, Running};
- enum class Messages : uint8_t {GoToSleep, GoToRunning, UpdateDateTime, UpdateBleConnection, UpdateBatteryLevel, TouchEvent, ButtonPushed,
- NewNotification, BleFirmwareUpdateStarted };
-
enum class FullRefreshDirections { None, Up, Down };
enum class TouchModes { Gestures, Polling };
@@ -44,9 +44,11 @@ namespace Pinetime {
Controllers::DateTime &dateTimeController, Drivers::WatchdogView &watchdog,
System::SystemTask &systemTask,
Pinetime::Controllers::NotificationManager& notificationManager,
- Pinetime::Controllers::HeartRateController& heartRateController);
+ Pinetime::Controllers::HeartRateController& heartRateController,
+ Controllers::Settings &settingsController
+ );
void Start();
- void PushMessage(Messages msg);
+ void PushMessage(Display::Messages msg);
void StartApp(Apps app);
@@ -89,6 +91,7 @@ namespace Pinetime {
Pinetime::Controllers::FirmwareValidator validator;
TouchModes touchMode = TouchModes::Gestures;
Pinetime::Controllers::HeartRateController& heartRateController;
+ Pinetime::Controllers::Settings& settingsController;
};
}
}
diff --git a/src/displayapp/DisplayAppRecovery.cpp b/src/displayapp/DisplayAppRecovery.cpp
new file mode 100644
index 00000000..9cf76953
--- /dev/null
+++ b/src/displayapp/DisplayAppRecovery.cpp
@@ -0,0 +1,110 @@
+#include "DisplayAppRecovery.h"
+#include
+#include
+#include
+#include
+#include "displayapp/icons/infinitime/infinitime-nb.c"
+
+using namespace Pinetime::Applications;
+
+DisplayApp::DisplayApp(Drivers::St7789 &lcd, Components::LittleVgl &lvgl, Drivers::Cst816S &touchPanel,
+ Controllers::Battery &batteryController, Controllers::Ble &bleController,
+ Controllers::DateTime &dateTimeController, Drivers::WatchdogView &watchdog,
+ System::SystemTask &systemTask,
+ Pinetime::Controllers::NotificationManager& notificationManager,
+ Pinetime::Controllers::HeartRateController& heartRateController,
+ Pinetime::Controllers::Settings& settingsController):
+ lcd{lcd}, bleController{bleController} {
+ msgQueue = xQueueCreate(queueSize, itemSize);
+
+}
+
+void DisplayApp::Start() {
+ if (pdPASS != xTaskCreate(DisplayApp::Process, "displayapp", 512, this, 0, &taskHandle))
+ APP_ERROR_HANDLER(NRF_ERROR_NO_MEM);
+}
+
+void DisplayApp::Process(void *instance) {
+ auto *app = static_cast(instance);
+ NRF_LOG_INFO("displayapp task started!");
+
+ // Send a dummy notification to unlock the lvgl display driver for the first iteration
+ xTaskNotifyGive(xTaskGetCurrentTaskHandle());
+
+ app->InitHw();
+ while (1) {
+ app->Refresh();
+ }
+}
+
+void DisplayApp::InitHw() {
+ DisplayLogo(colorWhite);
+}
+
+void DisplayApp::Refresh() {
+ Display::Messages msg;
+ if (xQueueReceive(msgQueue, &msg, 200)) {
+ switch (msg) {
+ case Display::Messages::UpdateBleConnection:
+ if (bleController.IsConnected())
+ DisplayLogo(colorBlue);
+ else
+ DisplayLogo(colorWhite);
+ break;
+ case Display::Messages::BleFirmwareUpdateStarted:
+ DisplayLogo(colorGreen);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (bleController.IsFirmwareUpdating()) {
+ uint8_t percent = (static_cast(bleController.FirmwareUpdateCurrentBytes()) /
+ static_cast(bleController.FirmwareUpdateTotalBytes())) * 100.0f;
+ switch (bleController.State()) {
+ case Controllers::Ble::FirmwareUpdateStates::Running:
+ DisplayOtaProgress(percent, colorWhite);
+ break;
+ case Controllers::Ble::FirmwareUpdateStates::Validated:
+ DisplayOtaProgress(100, colorGreenSwapped);
+ break;
+ case Controllers::Ble::FirmwareUpdateStates::Error:
+ DisplayOtaProgress(100, colorRedSwapped);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void DisplayApp::DisplayLogo(uint16_t color) {
+ Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb), color, colorBlack);
+ for(int i = 0; i < displayWidth; i++) {
+ rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel);
+ ulTaskNotifyTake(pdTRUE, 500);
+ lcd.BeginDrawBuffer(0, i, displayWidth, 1);
+ lcd.NextDrawBuffer(reinterpret_cast(displayBuffer), displayWidth * bytesPerPixel);
+ }
+}
+
+void DisplayApp::DisplayOtaProgress(uint8_t percent, uint16_t color) {
+ const uint8_t barHeight = 20;
+ std::fill(displayBuffer, displayBuffer+(displayWidth * bytesPerPixel), color);
+ for(int i = 0; i < barHeight; i++) {
+ ulTaskNotifyTake(pdTRUE, 500);
+ uint16_t barWidth = std::min(static_cast(percent) * 2.4f, static_cast(displayWidth));
+ lcd.BeginDrawBuffer(0, displayWidth - barHeight + i, barWidth, 1);
+ lcd.NextDrawBuffer(reinterpret_cast(displayBuffer), barWidth * bytesPerPixel);
+ }
+}
+
+void DisplayApp::PushMessage(Display::Messages msg) {
+ BaseType_t xHigherPriorityTaskWoken;
+ xHigherPriorityTaskWoken = pdFALSE;
+ xQueueSendFromISR(msgQueue, &msg, &xHigherPriorityTaskWoken);
+ if (xHigherPriorityTaskWoken) {
+ /* Actual macro used here is port specific. */
+ // TODO : should I do something here?
+ }
+}
\ No newline at end of file
diff --git a/src/displayapp/DisplayAppRecovery.h b/src/displayapp/DisplayAppRecovery.h
new file mode 100644
index 00000000..a3f27d2c
--- /dev/null
+++ b/src/displayapp/DisplayAppRecovery.h
@@ -0,0 +1,73 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#include
+#include "components/gfx/Gfx.h"
+#include "components/battery/BatteryController.h"
+#include "components/brightness/BrightnessController.h"
+#include "components/ble/BleController.h"
+#include "components/datetime/DateTimeController.h"
+#include "components/ble/NotificationManager.h"
+#include "components/firmwarevalidator/FirmwareValidator.h"
+#include "drivers/Cst816s.h"
+#include
+#include
+#include
+#include
+#include "TouchEvents.h"
+#include "Apps.h"
+#include "Messages.h"
+#include "DummyLittleVgl.h"
+
+namespace Pinetime {
+ namespace System {
+ class SystemTask;
+ };
+ namespace Applications {
+ class DisplayApp {
+ public:
+ DisplayApp(Drivers::St7789 &lcd, Components::LittleVgl &lvgl, Drivers::Cst816S &,
+ Controllers::Battery &batteryController, Controllers::Ble &bleController,
+ Controllers::DateTime &dateTimeController, Drivers::WatchdogView &watchdog,
+ System::SystemTask &systemTask,
+ Pinetime::Controllers::NotificationManager& notificationManager,
+ Pinetime::Controllers::HeartRateController& heartRateController,
+ Pinetime::Controllers::Settings& settingsController);
+ void Start();
+ void PushMessage(Pinetime::Applications::Display::Messages msg);
+
+ private:
+ TaskHandle_t taskHandle;
+ static void Process(void* instance);
+ void DisplayLogo(uint16_t color);
+ void DisplayOtaProgress(uint8_t percent, uint16_t color);
+ void InitHw();
+ void Refresh();
+ Pinetime::Drivers::St7789& lcd;
+ Controllers::Ble &bleController;
+
+ static constexpr uint8_t queueSize = 10;
+ static constexpr uint8_t itemSize = 1;
+ QueueHandle_t msgQueue;
+ static constexpr uint8_t displayWidth = 240;
+ static constexpr uint8_t displayHeight = 240;
+ static constexpr uint8_t bytesPerPixel = 2;
+
+ static constexpr uint16_t colorWhite = 0xFFFF;
+ static constexpr uint16_t colorGreen = 0x07E0;
+ static constexpr uint16_t colorGreenSwapped = 0xE007;
+ static constexpr uint16_t colorBlue = 0x0000ff;
+ static constexpr uint16_t colorRed = 0xff00;
+ static constexpr uint16_t colorRedSwapped = 0x00ff;
+ static constexpr uint16_t colorBlack = 0x0000;
+ uint8_t displayBuffer[displayWidth * bytesPerPixel];
+
+
+ };
+ }
+}
+
+
diff --git a/src/displayapp/DummyLittleVgl.h b/src/displayapp/DummyLittleVgl.h
new file mode 100644
index 00000000..1c60911c
--- /dev/null
+++ b/src/displayapp/DummyLittleVgl.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include
+
+namespace Pinetime {
+ namespace Components {
+ class LittleVgl {
+ public:
+ enum class FullRefreshDirections { None, Up, Down };
+ 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 FlushDisplay(const lv_area_t * area, lv_color_t * color_p) {}
+ bool GetTouchPadInfo(lv_indev_data_t *ptr) {return false;}
+ void SetFullRefresh(FullRefreshDirections direction) {}
+ void SetNewTapEvent(uint16_t x, uint16_t y) {}
+
+
+ };
+ }
+}
+
diff --git a/src/displayapp/LittleVgl.cpp b/src/displayapp/LittleVgl.cpp
index 44fa5657..238164a8 100644
--- a/src/displayapp/LittleVgl.cpp
+++ b/src/displayapp/LittleVgl.cpp
@@ -67,65 +67,47 @@ void LittleVgl::SetFullRefresh(FullRefreshDirections direction) {
}
void LittleVgl::FlushDisplay(const lv_area_t *area, lv_color_t *color_p) {
+ uint16_t y1, y2, width, height = 0;
+
ulTaskNotifyTake(pdTRUE, 500);
// Notification is still needed (even if there is a mutex on SPI) because of the DataCommand pin
// which cannot be set/clear during a transfer.
+ if( (scrollDirection == LittleVgl::FullRefreshDirections::Down) && (area->y2 == visibleNbLines - 1)) {
+ writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines;
+ } else if( (scrollDirection == FullRefreshDirections::Up) && (area->y1 == 0) ) {
+ writeOffset = (writeOffset + visibleNbLines) % totalNbLines;
+ }
- // TODO refactore and remove duplicated code
+ y1 = (area->y1 + writeOffset) % totalNbLines;
+ y2 = (area->y2 + writeOffset) % totalNbLines;
+
+ width = (area->x2 - area->x1) + 1;
+ height = (area->y2 - area->y1) + 1;
- uint16_t x, y, y1, y2, width, height = 0;
if(scrollDirection == LittleVgl::FullRefreshDirections::Down) {
- if(area->y2 == visibleNbLines-1) {
- writeOffset = ((writeOffset + totalNbLines) - visibleNbLines) % totalNbLines;
- }
- x = area->x1;
- width = (area->x2 - area->x1) + 1;
-
- y1 = (area->y1 + writeOffset) % totalNbLines;
- y2 = (area->y2 + writeOffset) % totalNbLines;
- y = y1;
- height = (y2 - y1) + 1;
-
if(area->y2 < visibleNbLines - 1) {
uint16_t toScroll = 0;
if(area->y1 == 0) {
- toScroll = height*2;
+ toScroll = height * 2;
scrollDirection = FullRefreshDirections::None;
lv_disp_set_direction(lv_disp_get_default(), 0);
} else {
toScroll = height;
}
-
if(scrollOffset >= toScroll)
scrollOffset -= toScroll;
else {
toScroll -= scrollOffset;
- scrollOffset = (totalNbLines) - toScroll;
+ scrollOffset = (totalNbLines) - toScroll;
}
-
- lcd.VerticalScrollDefinition(0, 320, 0);
lcd.VerticalScrollStartAddress(scrollOffset);
}
- lcd.BeginDrawBuffer(x, y, width, height);
- lcd.NextDrawBuffer(reinterpret_cast(color_p), width * height*2) ;
-
} else if(scrollDirection == FullRefreshDirections::Up) {
- if(area->y1 == 0) {
- writeOffset = (writeOffset + visibleNbLines) % totalNbLines;
- }
-
- x = area->x1;
- width = (area->x2 - area->x1) + 1;
-
- y1 = (area->y1 + writeOffset) % totalNbLines;
- y2 = (area->y2 + writeOffset) % totalNbLines;
- y = y1;
- height = (y2 - y1) + 1;
if(area->y1 > 0) {
- if(area->y2 == visibleNbLines -1) {
+ if(area->y2 == visibleNbLines - 1) {
scrollOffset += (height * 2);
scrollDirection = FullRefreshDirections::None;
lv_disp_set_direction(lv_disp_get_default(), 0);
@@ -133,36 +115,27 @@ void LittleVgl::FlushDisplay(const lv_area_t *area, lv_color_t *color_p) {
scrollOffset += height;
}
scrollOffset = scrollOffset % totalNbLines;
- lcd.VerticalScrollDefinition(0, 320, 0);
lcd.VerticalScrollStartAddress(scrollOffset);
}
-
- lcd.BeginDrawBuffer(x, y, width, height);
- lcd.NextDrawBuffer(reinterpret_cast(color_p), width * height*2);
- } else {
- x = area->x1;
- width = (area->x2 - area->x1) + 1;
- y1 = (area->y1 + writeOffset) % totalNbLines;
- y2 = (area->y2 + writeOffset) % totalNbLines;
- y = y1;
- height = (y2 - y1) + 1;
-
- if (y2 < y1) {
- height = (totalNbLines - 1) - y1;
- lcd.BeginDrawBuffer(x, y1, width, height);
- lcd.NextDrawBuffer(reinterpret_cast(color_p), width * height * 2);
- ulTaskNotifyTake(pdTRUE, 500);
- height = y2;
- lcd.BeginDrawBuffer(x, 0, width, height);
- lcd.NextDrawBuffer(reinterpret_cast(color_p), width * height * 2);
- } else {
- lcd.BeginDrawBuffer(x, y, width, height);
- lcd.NextDrawBuffer(reinterpret_cast(color_p), width * height * 2);
- }
}
- /* IMPORTANT!!!
- * Inform the graphics library that you are ready with the flushing*/
+ 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, 320);
+ }
+ 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);
}
diff --git a/src/displayapp/Messages.h b/src/displayapp/Messages.h
new file mode 100644
index 00000000..f617774d
--- /dev/null
+++ b/src/displayapp/Messages.h
@@ -0,0 +1,11 @@
+#pragma once
+namespace Pinetime {
+ namespace Applications {
+ namespace Display {
+ enum class Messages : uint8_t {
+ GoToSleep, GoToRunning, UpdateDateTime, UpdateBleConnection, UpdateBatteryLevel, TouchEvent, ButtonPushed,
+ NewNotification, BleFirmwareUpdateStarted
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/displayapp/icons/bg_clock.c b/src/displayapp/icons/bg_clock.c
new file mode 100644
index 00000000..a9de4146
--- /dev/null
+++ b/src/displayapp/icons/bg_clock.c
@@ -0,0 +1,272 @@
+#if defined(LV_LVGL_H_INCLUDE_SIMPLE)
+#include "lvgl.h"
+#else
+#include "lvgl/lvgl.h"
+#endif
+
+
+#ifndef LV_ATTRIBUTE_MEM_ALIGN
+#define LV_ATTRIBUTE_MEM_ALIGN
+#endif
+
+#ifndef LV_ATTRIBUTE_IMG_BG_CLOCK
+#define LV_ATTRIBUTE_IMG_BG_CLOCK
+#endif
+
+const LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST LV_ATTRIBUTE_IMG_BG_CLOCK uint8_t bg_clock_map[] = {
+ 0x00, 0x00, 0x00, 0xff, /*Color of index 0*/
+ 0x68, 0x5b, 0x44, 0xff, /*Color of index 1*/
+ 0xde, 0xa5, 0x33, 0xff, /*Color of index 2*/
+ 0xff, 0xff, 0xff, 0xff, /*Color of index 3*/
+
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xa4, 0x02, 0xaa, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa8, 0x0a, 0xaa, 0xaa, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x6a, 0xa8, 0x0a, 0x40, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0xa8, 0x04, 0x00, 0x02, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0xa8, 0x00, 0x00, 0x01, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x01, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x01, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x02, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x06, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x0a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x2a, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x6a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x01, 0xa8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x06, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x1a, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0x2a, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x00, 0xa9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x02, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x0a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x1a, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x1a, 0xaa, 0xaa, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa8, 0x1a, 0xaa, 0xaa, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x05, 0x55, 0x55, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x07, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xd0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0f, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xf4, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xf4, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x40, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x05, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x50, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00,
+ 0x00, 0x00, 0x15, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x54, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x15, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x54, 0x00,
+ 0x00, 0x01, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x40, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x40,
+ 0x00, 0x15, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x50, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1a, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa,
+ 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x55, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x40,
+ 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x40,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
+ 0x00, 0x15, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x54, 0x00,
+ 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x05, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x50, 0x00, 0x00,
+ 0x00, 0x00, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x55, 0x00, 0x00,
+ 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x55, 0x52, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xaa, 0xa8, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xaa, 0xa1, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x40, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x15, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x06, 0x45, 0x01, 0x45, 0x40, 0x00, 0x54, 0x00, 0x60, 0x01, 0x40, 0x45, 0x50, 0x15, 0x00, 0x05, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x54, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x02, 0x46, 0x02, 0xaa, 0xa4, 0x06, 0x9a, 0x40, 0x60, 0x01, 0x80, 0xaa, 0xa9, 0xaa, 0x80, 0x1a, 0x69, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x02, 0x46, 0x02, 0x90, 0x28, 0x19, 0x01, 0x80, 0x60, 0x01, 0x80, 0xa0, 0x1a, 0x40, 0x90, 0x64, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x0a, 0x06, 0x02, 0x80, 0x18, 0x18, 0x00, 0x90, 0x60, 0x01, 0x80, 0x90, 0x0a, 0x00, 0xa0, 0xa0, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xaa, 0xa9, 0x06, 0x02, 0x40, 0x18, 0x2a, 0xaa, 0x90, 0x60, 0x01, 0x80, 0x90, 0x09, 0x00, 0x60, 0xaa, 0xaa, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x95, 0x50, 0x06, 0x02, 0x40, 0x18, 0x28, 0x00, 0x00, 0x60, 0x01, 0x80, 0x90, 0x09, 0x00, 0x60, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x06, 0x02, 0x40, 0x18, 0x19, 0x00, 0x00, 0x60, 0x01, 0x80, 0x90, 0x09, 0x00, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x06, 0x02, 0x40, 0x18, 0x0a, 0x56, 0x80, 0x60, 0x01, 0x80, 0x90, 0x09, 0x00, 0x60, 0x29, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x06, 0x02, 0x40, 0x18, 0x01, 0xa9, 0x00, 0x60, 0x01, 0x80, 0x90, 0x09, 0x00, 0x60, 0x06, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xd0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x1f, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xf4, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x0f, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xf0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x40, 0x00, 0x01, 0x40, 0x00, 0x0a, 0xa0, 0x00, 0x01, 0x00, 0x00, 0x01, 0x40, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x01, 0x40, 0x00, 0x0a, 0xa0, 0x00, 0x01, 0x40, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x01, 0x40, 0x00, 0x0a, 0xa0, 0x00, 0x01, 0x40, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0x40, 0x00, 0x0a, 0xa0, 0x00, 0x01, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x40, 0x00, 0x0a, 0xa0, 0x00, 0x01, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+
+const lv_img_dsc_t bg_clock = {
+ .header.always_zero = 0,
+ .header.w = 240,
+ .header.h = 240,
+ .data_size = 14416,
+ .header.cf = LV_IMG_CF_INDEXED_2BIT,
+ .data = bg_clock_map,
+};
+
diff --git a/src/displayapp/icons/infinitime/infinitime-nb.c b/src/displayapp/icons/infinitime/infinitime-nb.c
new file mode 100644
index 00000000..52f18541
--- /dev/null
+++ b/src/displayapp/icons/infinitime/infinitime-nb.c
@@ -0,0 +1,127 @@
+
+#include
+
+// 1-bit RLE, generated from ./infinitime-nb.png, 1445 bytes
+static const uint8_t infinitime_nb[] = {
+ 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0,
+ 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0,
+ 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0x66, 0x2, 0xed, 0x4, 0xec, 0x5,
+ 0xea, 0x7, 0xe8, 0x9, 0xe6, 0xa, 0xe5, 0xc, 0xe3, 0xe, 0xe1, 0x10,
+ 0xdf, 0x12, 0xde, 0x12, 0xdd, 0x14, 0xdb, 0x16, 0xd9, 0x18, 0xd7, 0x1a,
+ 0xd5, 0x1b, 0xd4, 0x1d, 0xd3, 0xd, 0x3, 0xe, 0xd1, 0xd, 0x5, 0xe,
+ 0xcf, 0xe, 0x5, 0xf, 0xcd, 0xf, 0x5, 0xf, 0xcc, 0x10, 0x5, 0x10,
+ 0xca, 0x11, 0x5, 0x11, 0xc8, 0x12, 0x5, 0x12, 0xc6, 0x13, 0x5, 0x13,
+ 0xc5, 0x13, 0x5, 0x13, 0xc4, 0x14, 0x5, 0x14, 0xc2, 0x15, 0x5, 0x15,
+ 0xc0, 0x17, 0x3, 0x17, 0xbe, 0x33, 0xbc, 0x34, 0xbb, 0x36, 0xba, 0x37,
+ 0xb8, 0x39, 0xb6, 0x3b, 0xb4, 0x3c, 0xb3, 0x3e, 0xb1, 0x40, 0xaf, 0x9,
+ 0x2, 0x2e, 0x1, 0x8, 0xad, 0x9, 0x4, 0x2c, 0x3, 0x8, 0xac, 0x8,
+ 0x6, 0x2a, 0x5, 0x7, 0xab, 0x9, 0x6, 0x29, 0x6, 0x8, 0xa9, 0xb,
+ 0x5, 0x29, 0x5, 0xa, 0xa7, 0xd, 0x3, 0x2b, 0x3, 0xc, 0xa5, 0x4c,
+ 0xa3, 0x4d, 0xa2, 0x4f, 0xa0, 0x51, 0x9f, 0x52, 0x9d, 0x54, 0x9b, 0x55,
+ 0x9a, 0x57, 0x98, 0x59, 0x96, 0x5b, 0x94, 0x5d, 0x93, 0x5d, 0x92, 0x5f,
+ 0x90, 0x61, 0x8e, 0x63, 0x8c, 0x65, 0x8a, 0x66, 0x89, 0x68, 0x87, 0x8,
+ 0x2, 0x59, 0x2, 0x5, 0x86, 0x7, 0x4, 0x57, 0x4, 0x5, 0x84, 0x8,
+ 0x5, 0x55, 0x6, 0x5, 0x82, 0x9, 0x6, 0x54, 0x6, 0x5, 0x81, 0xa,
+ 0x5, 0x55, 0x5, 0x7, 0x7f, 0xc, 0x4, 0x56, 0x3, 0x9, 0x7d, 0x74,
+ 0x7b, 0x76, 0x79, 0x77, 0x79, 0x78, 0x77, 0x7a, 0x75, 0x7c, 0x73, 0x7e,
+ 0x71, 0x7f, 0x70, 0x81, 0x6e, 0x83, 0x6c, 0x85, 0x6b, 0x86, 0x69, 0x87,
+ 0x68, 0x89, 0x66, 0x8b, 0x64, 0x8d, 0x62, 0x8f, 0x60, 0x90, 0x60, 0x91,
+ 0x5e, 0x93, 0x5c, 0x95, 0x5a, 0xe, 0x7, 0x71, 0x7, 0xa, 0x58, 0xd,
+ 0xb, 0x6d, 0xb, 0x8, 0x57, 0xe, 0xc, 0x6c, 0xc, 0x8, 0x55, 0xf,
+ 0xc, 0x6c, 0xb, 0xa, 0x53, 0x11, 0xa, 0x6d, 0xb, 0xb, 0x52, 0x9f,
+ 0x50, 0xa0, 0x4f, 0xa2, 0x4d, 0xa4, 0x4b, 0xa6, 0x49, 0xa8, 0x48, 0xa8,
+ 0xff, 0x0, 0xe3, 0x44, 0xad, 0x43, 0xae, 0x41, 0xb0, 0x40, 0xb1, 0x3e,
+ 0xb2, 0x3e, 0xb3, 0x3c, 0xb5, 0x3a, 0xb7, 0x39, 0xb8, 0x37, 0xb9, 0x36,
+ 0xbb, 0x35, 0xe, 0x1, 0x66, 0x1, 0x3c, 0x1, 0x9, 0x33, 0xe, 0x3,
+ 0x15, 0x5, 0xe, 0x4, 0x16, 0x15, 0xd, 0x3, 0x11, 0x5, 0xe, 0x4,
+ 0x12, 0x3, 0x9, 0x31, 0xf, 0x4, 0x14, 0x6, 0xd, 0x4, 0x16, 0x15,
+ 0xd, 0x4, 0x10, 0x5, 0xe, 0x4, 0x12, 0x4, 0x9, 0x30, 0xf, 0x4,
+ 0x14, 0x6, 0xd, 0x4, 0x16, 0x15, 0xd, 0x4, 0x10, 0x6, 0xd, 0x4,
+ 0x12, 0x4, 0x9, 0x2f, 0x10, 0x4, 0x14, 0x7, 0xc, 0x4, 0x16, 0x15,
+ 0xd, 0x4, 0x10, 0x6, 0xd, 0x4, 0x12, 0x4, 0xa, 0x2d, 0x11, 0x4,
+ 0x14, 0x7, 0xc, 0x4, 0x16, 0x4, 0x1e, 0x4, 0x10, 0x7, 0xc, 0x4,
+ 0x12, 0x4, 0xb, 0x2c, 0x11, 0x4, 0x14, 0x8, 0xb, 0x4, 0x16, 0x4,
+ 0x1e, 0x4, 0x10, 0x7, 0xc, 0x4, 0x12, 0x4, 0xc, 0x2a, 0x12, 0x4,
+ 0x14, 0x8, 0xb, 0x4, 0x16, 0x4, 0x1e, 0x4, 0x10, 0x8, 0xb, 0x4,
+ 0x12, 0x4, 0xd, 0x28, 0x13, 0x4, 0x14, 0x4, 0x1, 0x4, 0xa, 0x4,
+ 0x16, 0x4, 0x1e, 0x4, 0x10, 0x4, 0x1, 0x3, 0xb, 0x4, 0x12, 0x4,
+ 0xd, 0x28, 0x13, 0x4, 0x14, 0x4, 0x1, 0x4, 0xa, 0x4, 0x16, 0x4,
+ 0x1e, 0x4, 0x10, 0x4, 0x1, 0x4, 0xa, 0x4, 0x12, 0x4, 0xe, 0x26,
+ 0x14, 0x4, 0x14, 0x4, 0x2, 0x4, 0x9, 0x4, 0x16, 0x4, 0x1e, 0x4,
+ 0x10, 0x4, 0x2, 0x3, 0xa, 0x4, 0x12, 0x4, 0xf, 0x24, 0x15, 0x4,
+ 0x14, 0x4, 0x2, 0x4, 0x9, 0x4, 0x16, 0x4, 0x1e, 0x4, 0x10, 0x4,
+ 0x2, 0x4, 0x9, 0x4, 0x12, 0x4, 0x10, 0x23, 0x15, 0x4, 0x14, 0x4,
+ 0x3, 0x4, 0x8, 0x4, 0x16, 0x4, 0x1e, 0x4, 0x10, 0x4, 0x2, 0x4,
+ 0x9, 0x4, 0x12, 0x4, 0x11, 0x21, 0x16, 0x4, 0x14, 0x4, 0x3, 0x4,
+ 0x8, 0x4, 0x16, 0x4, 0x1e, 0x4, 0x10, 0x4, 0x3, 0x4, 0x8, 0x4,
+ 0x12, 0x4, 0x11, 0x20, 0x17, 0x4, 0x14, 0x4, 0x4, 0x3, 0x8, 0x4,
+ 0x16, 0x4, 0x1e, 0x4, 0x10, 0x4, 0x3, 0x4, 0x8, 0x4, 0x12, 0x4,
+ 0x12, 0x1f, 0x17, 0x4, 0x14, 0x4, 0x4, 0x4, 0x7, 0x4, 0x16, 0x4,
+ 0x1e, 0x4, 0x10, 0x4, 0x4, 0x3, 0x8, 0x4, 0x12, 0x4, 0x13, 0x1d,
+ 0x18, 0x4, 0x14, 0x4, 0x5, 0x3, 0x7, 0x4, 0x16, 0x13, 0xf, 0x4,
+ 0x10, 0x4, 0x4, 0x4, 0x7, 0x4, 0x12, 0x4, 0x14, 0x1b, 0x1a, 0x3,
+ 0x14, 0x4, 0x5, 0x4, 0x6, 0x4, 0x16, 0x13, 0x10, 0x3, 0x10, 0x4,
+ 0x5, 0x3, 0x7, 0x4, 0x13, 0x3, 0x15, 0x1a, 0x1b, 0x1, 0x15, 0x4,
+ 0x6, 0x3, 0x6, 0x4, 0x16, 0x13, 0x11, 0x1, 0x11, 0x4, 0x5, 0x4,
+ 0x6, 0x4, 0x14, 0x1, 0x16, 0x19, 0x32, 0x4, 0x6, 0x4, 0x5, 0x4,
+ 0x16, 0x13, 0x23, 0x4, 0x6, 0x3, 0x6, 0x4, 0x2c, 0x17, 0x33, 0x4,
+ 0x7, 0x3, 0x5, 0x4, 0x16, 0x4, 0x32, 0x4, 0x6, 0x4, 0x5, 0x4,
+ 0x2d, 0x16, 0x1d, 0x1, 0x15, 0x4, 0x7, 0x4, 0x4, 0x4, 0x16, 0x4,
+ 0x20, 0x1, 0x11, 0x4, 0x7, 0x3, 0x5, 0x4, 0x14, 0x1, 0x19, 0x14,
+ 0x1d, 0x3, 0x14, 0x4, 0x7, 0x4, 0x4, 0x4, 0x16, 0x4, 0x1f, 0x3,
+ 0x10, 0x4, 0x7, 0x4, 0x4, 0x4, 0x13, 0x3, 0x19, 0x12, 0x1d, 0x4,
+ 0x14, 0x4, 0x8, 0x4, 0x3, 0x4, 0x16, 0x4, 0x1e, 0x4, 0x10, 0x4,
+ 0x8, 0x3, 0x4, 0x4, 0x12, 0x4, 0x19, 0x12, 0x1d, 0x4, 0x14, 0x4,
+ 0x8, 0x4, 0x3, 0x4, 0x16, 0x4, 0x1e, 0x4, 0x10, 0x4, 0x8, 0x4,
+ 0x3, 0x4, 0x12, 0x4, 0x1a, 0x10, 0x1e, 0x4, 0x14, 0x4, 0x9, 0x3,
+ 0x3, 0x4, 0x16, 0x4, 0x1e, 0x4, 0x10, 0x4, 0x8, 0x4, 0x3, 0x4,
+ 0x12, 0x4, 0x1b, 0xe, 0x1f, 0x4, 0x14, 0x4, 0x9, 0x4, 0x2, 0x4,
+ 0x16, 0x4, 0x1e, 0x4, 0x10, 0x4, 0x9, 0x4, 0x2, 0x4, 0x12, 0x4,
+ 0x1c, 0xd, 0x1f, 0x4, 0x14, 0x4, 0xa, 0x3, 0x2, 0x4, 0x16, 0x4,
+ 0x1e, 0x4, 0x10, 0x4, 0x9, 0x4, 0x2, 0x4, 0x12, 0x4, 0x1d, 0xb,
+ 0x20, 0x4, 0x14, 0x4, 0xa, 0x4, 0x1, 0x4, 0x16, 0x4, 0x1e, 0x4,
+ 0x10, 0x4, 0xa, 0x3, 0x2, 0x4, 0x12, 0x4, 0x1d, 0xb, 0x20, 0x4,
+ 0x14, 0x4, 0xb, 0x3, 0x1, 0x4, 0x16, 0x4, 0x1e, 0x4, 0x10, 0x4,
+ 0xa, 0x4, 0x1, 0x4, 0x12, 0x4, 0x1e, 0x9, 0x21, 0x4, 0x14, 0x4,
+ 0xb, 0x8, 0x16, 0x4, 0x1e, 0x4, 0x10, 0x4, 0xb, 0x3, 0x1, 0x4,
+ 0x12, 0x4, 0x1f, 0x7, 0x22, 0x4, 0x14, 0x4, 0xc, 0x7, 0x16, 0x4,
+ 0x1e, 0x4, 0x10, 0x4, 0xb, 0x8, 0x12, 0x4, 0x20, 0x6, 0x22, 0x4,
+ 0x14, 0x4, 0xc, 0x7, 0x16, 0x4, 0x1e, 0x4, 0x10, 0x4, 0xc, 0x7,
+ 0x12, 0x4, 0x21, 0x4, 0x23, 0x4, 0x14, 0x4, 0xd, 0x6, 0x16, 0x4,
+ 0x1e, 0x4, 0x10, 0x4, 0xc, 0x7, 0x12, 0x4, 0x21, 0x3, 0x24, 0x4,
+ 0x14, 0x4, 0xd, 0x6, 0x16, 0x4, 0x1e, 0x4, 0x10, 0x4, 0xd, 0x6,
+ 0x12, 0x4, 0x22, 0x2, 0x24, 0x4, 0x14, 0x4, 0xd, 0x6, 0x16, 0x4,
+ 0x1e, 0x4, 0x10, 0x4, 0xd, 0x6, 0x12, 0x4, 0x48, 0x3, 0x15, 0x4,
+ 0xe, 0x5, 0x16, 0x4, 0x1e, 0x3, 0x11, 0x4, 0xd, 0x6, 0x12, 0x3,
+ 0x4a, 0x1, 0x66, 0x1, 0x3c, 0x1, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0,
+ 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0x10, 0x11,
+ 0xf, 0x9, 0xf, 0x4, 0x9, 0x4, 0xd, 0xf, 0x8b, 0x11, 0xf, 0x9,
+ 0xf, 0x5, 0x7, 0x5, 0xd, 0xf, 0x8b, 0x11, 0xf, 0x9, 0xf, 0x5,
+ 0x7, 0x5, 0xd, 0xf, 0x92, 0x3, 0x19, 0x3, 0x12, 0x6, 0x5, 0x6,
+ 0xd, 0x3, 0x9e, 0x3, 0x19, 0x3, 0x12, 0x6, 0x5, 0x6, 0xd, 0x3,
+ 0x9e, 0x3, 0x19, 0x3, 0x12, 0x6, 0x5, 0x6, 0xd, 0x3, 0x9e, 0x3,
+ 0x19, 0x3, 0x12, 0x3, 0x1, 0x3, 0x3, 0x3, 0x1, 0x3, 0xd, 0x3,
+ 0x9e, 0x3, 0x19, 0x3, 0x12, 0x3, 0x2, 0x2, 0x3, 0x2, 0x2, 0x3,
+ 0xd, 0x3, 0x9e, 0x3, 0x19, 0x3, 0x12, 0x3, 0x2, 0x3, 0x1, 0x3,
+ 0x2, 0x3, 0xd, 0x3, 0x9e, 0x3, 0x19, 0x3, 0x12, 0x3, 0x2, 0x3,
+ 0x1, 0x3, 0x2, 0x3, 0xd, 0x3, 0x9e, 0x3, 0x19, 0x3, 0x12, 0x3,
+ 0x3, 0x5, 0x3, 0x3, 0xd, 0xd, 0x94, 0x3, 0x19, 0x3, 0x12, 0x3,
+ 0x3, 0x5, 0x3, 0x3, 0xd, 0xd, 0x94, 0x3, 0x19, 0x3, 0x12, 0x3,
+ 0x4, 0x3, 0x4, 0x3, 0xd, 0xd, 0x94, 0x3, 0x19, 0x3, 0x12, 0x3,
+ 0x4, 0x3, 0x4, 0x3, 0xd, 0x3, 0x9e, 0x3, 0x19, 0x3, 0x12, 0x3,
+ 0x5, 0x1, 0x5, 0x3, 0xd, 0x3, 0x9e, 0x3, 0x19, 0x3, 0x12, 0x3,
+ 0x5, 0x1, 0x5, 0x3, 0xd, 0x3, 0x9e, 0x3, 0x19, 0x3, 0x12, 0x3,
+ 0xb, 0x3, 0xd, 0x3, 0x9e, 0x3, 0x19, 0x3, 0x12, 0x3, 0xb, 0x3,
+ 0xd, 0x3, 0x9e, 0x3, 0x19, 0x3, 0x12, 0x3, 0xb, 0x3, 0xd, 0x3,
+ 0x9e, 0x3, 0x19, 0x3, 0x12, 0x3, 0xb, 0x3, 0xd, 0x3, 0x9e, 0x3,
+ 0x19, 0x3, 0x12, 0x3, 0xb, 0x3, 0xd, 0x3, 0x9e, 0x3, 0x19, 0x3,
+ 0x12, 0x3, 0xb, 0x3, 0xd, 0xf, 0x92, 0x3, 0x16, 0x9, 0xf, 0x3,
+ 0xb, 0x3, 0xd, 0xf, 0x92, 0x3, 0x16, 0x9, 0xf, 0x3, 0xb, 0x3,
+ 0xd, 0xf, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0,
+ 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0,
+ 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0,
+ 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0,
+ 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0,
+ 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0, 0xff, 0x0,
+ 0xff, 0x0, 0xff, 0x0, 0xec,
+};
diff --git a/src/displayapp/icons/infinitime/infinitime-nb.png b/src/displayapp/icons/infinitime/infinitime-nb.png
new file mode 100644
index 00000000..e425b060
Binary files /dev/null and b/src/displayapp/icons/infinitime/infinitime-nb.png differ
diff --git a/src/displayapp/screens/ApplicationList.cpp b/src/displayapp/screens/ApplicationList.cpp
index f9ff49e3..dd9cb2a8 100644
--- a/src/displayapp/screens/ApplicationList.cpp
+++ b/src/displayapp/screens/ApplicationList.cpp
@@ -8,13 +8,18 @@
using namespace Pinetime::Applications::Screens;
-ApplicationList::ApplicationList(Pinetime::Applications::DisplayApp *app) :
+ApplicationList::ApplicationList(Pinetime::Applications::DisplayApp *app,
+ Pinetime::Controllers::Settings &settingsController) :
Screen(app),
- screens{app, {
+ settingsController{settingsController},
+ screens{app,
+ settingsController.GetAppMenu(),
+ {
[this]() -> std::unique_ptr { return CreateScreen1(); },
[this]() -> std::unique_ptr { return CreateScreen2(); },
//[this]() -> std::unique_ptr { return CreateScreen3(); }
- }
+ },
+ Screens::ScreenListModes::UpDown
} {}
@@ -51,7 +56,7 @@ std::unique_ptr ApplicationList::CreateScreen1() {
};
- return std::unique_ptr(new Screens::Tile(app, applications));
+ return std::unique_ptr(new Screens::Tile(0, app, settingsController, applications));
}
std::unique_ptr ApplicationList::CreateScreen2() {
@@ -65,7 +70,7 @@ std::unique_ptr ApplicationList::CreateScreen2() {
}
};
- return std::unique_ptr(new Screens::Tile(app, applications));
+ return std::unique_ptr(new Screens::Tile(1, app, settingsController, applications));
}
std::unique_ptr ApplicationList::CreateScreen3() {
@@ -79,6 +84,6 @@ std::unique_ptr ApplicationList::CreateScreen3() {
}
};
- return std::unique_ptr(new Screens::Tile(app, applications));
+ return std::unique_ptr(new Screens::Tile(2, app, settingsController, applications));
}
diff --git a/src/displayapp/screens/ApplicationList.h b/src/displayapp/screens/ApplicationList.h
index aefb2385..0a0c6388 100644
--- a/src/displayapp/screens/ApplicationList.h
+++ b/src/displayapp/screens/ApplicationList.h
@@ -10,12 +10,16 @@ namespace Pinetime {
namespace Screens {
class ApplicationList : public Screen {
public:
- explicit ApplicationList(DisplayApp* app);
+ explicit ApplicationList(DisplayApp* app,
+ Pinetime::Controllers::Settings &settingsController);
~ApplicationList() override;
bool Refresh() override;
bool OnButtonPushed() override;
bool OnTouchEvent(TouchEvents event) override;
private:
+
+ Controllers::Settings& settingsController;
+
bool running = true;
ScreenList<2> screens;
diff --git a/src/displayapp/screens/Clock.cpp b/src/displayapp/screens/Clock.cpp
index eb0e37be..342dd222 100644
--- a/src/displayapp/screens/Clock.cpp
+++ b/src/displayapp/screens/Clock.cpp
@@ -10,248 +10,74 @@
#include "components/battery/BatteryController.h"
#include "components/ble/BleController.h"
#include "components/ble/NotificationManager.h"
-#include "components/heartrate/HeartRateController.h"
#include "../DisplayApp.h"
+#include "WatchFaceDigital.h"
+#include "WatchFaceAnalog.h"
+
using namespace Pinetime::Applications::Screens;
-namespace {
-
-char const *DaysString[] = {
- "",
- "MONDAY",
- "TUESDAY",
- "WEDNESDAY",
- "THURSDAY",
- "FRIDAY",
- "SATURDAY",
- "SUNDAY"
-};
-
-char const *MonthsString[] = {
- "",
- "JAN",
- "FEB",
- "MAR",
- "APR",
- "MAY",
- "JUN",
- "JUL",
- "AUG",
- "SEP",
- "OCT",
- "NOV",
- "DEC"
-};
-
-const char *MonthToString(Pinetime::Controllers::DateTime::Months month) {
- return MonthsString[static_cast(month)];
-}
-
-const char *DayOfWeekToString(Pinetime::Controllers::DateTime::Days dayOfWeek) {
- return DaysString[static_cast(dayOfWeek)];
-}
-
-}
-
-static void event_handler(lv_obj_t * obj, lv_event_t event) {
- Clock* screen = static_cast(obj->user_data);
- screen->OnObjectEvent(obj, event);
-}
-
Clock::Clock(DisplayApp* app,
Controllers::DateTime& dateTimeController,
Controllers::Battery& batteryController,
Controllers::Ble& bleController,
Controllers::NotificationManager& notificatioManager,
- Controllers::HeartRateController& heartRateController): Screen(app), currentDateTime{{}},
- dateTimeController{dateTimeController}, batteryController{batteryController},
- bleController{bleController}, notificatioManager{notificatioManager},
- heartRateController{heartRateController} {
- displayedChar[0] = 0;
- displayedChar[1] = 0;
- displayedChar[2] = 0;
- displayedChar[3] = 0;
- displayedChar[4] = 0;
+ Controllers::Settings &settingsController,
+ Controllers::HeartRateController& heartRateController) : Screen(app),
+ dateTimeController{dateTimeController}, batteryController{batteryController},
+ bleController{bleController}, notificatioManager{notificatioManager},
+ settingsController{settingsController},
+ heartRateController{heartRateController},
+ screens{app,
+ settingsController.GetClockFace(),
+ {
+ [this]() -> std::unique_ptr { return WatchFaceDigitalScreen(); },
+ [this]() -> std::unique_ptr { return WatchFaceAnalogScreen(); },
+ // Examples for more watch faces
+ //[this]() -> std::unique_ptr { return WatchFaceMinimalScreen(); },
+ //[this]() -> std::unique_ptr { return WatchFaceCustomScreen(); }
+ },
+ Screens::ScreenListModes::LongPress
+ } {
- batteryIcon = lv_label_create(lv_scr_act(), nullptr);
- lv_label_set_text(batteryIcon, Symbols::batteryFull);
- lv_obj_align(batteryIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, -5, 2);
+ settingsController.SetAppMenu(0);
- batteryPlug = lv_label_create(lv_scr_act(), nullptr);
- lv_label_set_text(batteryPlug, Symbols::plug);
- lv_obj_align(batteryPlug, batteryIcon, LV_ALIGN_OUT_LEFT_MID, -5, 0);
-
- bleIcon = lv_label_create(lv_scr_act(), nullptr);
- lv_label_set_text(bleIcon, Symbols::bluetooth);
- lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0);
-
- notificationIcon = lv_label_create(lv_scr_act(), NULL);
- lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
- lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 10, 0);
-
- label_date = lv_label_create(lv_scr_act(), nullptr);
-
- lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 60);
-
- label_time = lv_label_create(lv_scr_act(), nullptr);
-
- lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed);
-
- lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_LEFT_MID, 0, 0);
-
- backgroundLabel = lv_label_create(lv_scr_act(), nullptr);
- backgroundLabel->user_data = this;
- lv_obj_set_click(backgroundLabel, true);
- lv_obj_set_event_cb(backgroundLabel, event_handler);
- lv_label_set_long_mode(backgroundLabel, LV_LABEL_LONG_CROP);
- lv_obj_set_size(backgroundLabel, 240, 240);
- lv_obj_set_pos(backgroundLabel, 0, 0);
- lv_label_set_text(backgroundLabel, "");
-
-
- heartbeatIcon = lv_label_create(lv_scr_act(), nullptr);
- lv_label_set_text(heartbeatIcon, Symbols::heartBeat);
- lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2);
-
- heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
- lv_label_set_text(heartbeatValue, "0");
- lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
-
- heartbeatBpm = lv_label_create(lv_scr_act(), nullptr);
- lv_label_set_text(heartbeatBpm, "BPM");
- lv_obj_align(heartbeatBpm, heartbeatValue, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
-
- stepValue = lv_label_create(lv_scr_act(), nullptr);
- lv_label_set_text(stepValue, "0");
- lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -5, -2);
-
- stepIcon = lv_label_create(lv_scr_act(), nullptr);
- lv_label_set_text(stepIcon, Symbols::shoe);
- lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
-}
+ }
Clock::~Clock() {
lv_obj_clean(lv_scr_act());
}
-bool Clock::Refresh() {
- batteryPercentRemaining = batteryController.PercentRemaining();
- if (batteryPercentRemaining.IsUpdated()) {
- auto batteryPercent = batteryPercentRemaining.Get();
- lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryPercent));
- auto isCharging = batteryController.IsCharging() || batteryController.IsPowerPresent();
- lv_label_set_text(batteryPlug, BatteryIcon::GetPlugIcon(isCharging));
- }
-
- bleState = bleController.IsConnected();
- if (bleState.IsUpdated()) {
- if(bleState.Get() == true) {
- lv_label_set_text(bleIcon, BleIcon::GetIcon(true));
- } else {
- lv_label_set_text(bleIcon, BleIcon::GetIcon(false));
- }
- }
- lv_obj_align(batteryIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, -5, 5);
- lv_obj_align(batteryPlug, batteryIcon, LV_ALIGN_OUT_LEFT_MID, -5, 0);
- lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0);
-
- notificationState = notificatioManager.AreNewNotificationsAvailable();
- if(notificationState.IsUpdated()) {
- if(notificationState.Get() == true)
- lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(true));
- else
- lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
- }
-
- currentDateTime = dateTimeController.CurrentDateTime();
-
- if(currentDateTime.IsUpdated()) {
- auto newDateTime = currentDateTime.Get();
-
- auto dp = date::floor(newDateTime);
- auto time = date::make_time(newDateTime-dp);
- auto yearMonthDay = date::year_month_day(dp);
-
- auto year = (int)yearMonthDay.year();
- auto month = static_cast((unsigned)yearMonthDay.month());
- auto day = (unsigned)yearMonthDay.day();
- auto dayOfWeek = static_cast(date::weekday(yearMonthDay).iso_encoding());
-
- auto hour = time.hours().count();
- auto minute = time.minutes().count();
-
- char minutesChar[3];
- sprintf(minutesChar, "%02d", static_cast(minute));
-
- char hoursChar[3];
- sprintf(hoursChar, "%02d", static_cast(hour));
-
- char timeStr[6];
- sprintf(timeStr, "%c%c:%c%c", hoursChar[0],hoursChar[1],minutesChar[0], minutesChar[1]);
-
- if(hoursChar[0] != displayedChar[0] || hoursChar[1] != displayedChar[1] || minutesChar[0] != displayedChar[2] || minutesChar[1] != displayedChar[3]) {
- displayedChar[0] = hoursChar[0];
- displayedChar[1] = hoursChar[1];
- displayedChar[2] = minutesChar[0];
- displayedChar[3] = minutesChar[1];
-
- lv_label_set_text(label_time, timeStr);
- }
-
- if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) {
- char dateStr[22];
- sprintf(dateStr, "%s %d %s %d", DayOfWeekToString(dayOfWeek), day, MonthToString(month), year);
- lv_label_set_text(label_date, dateStr);
-
-
- currentYear = year;
- currentMonth = month;
- currentDayOfWeek = dayOfWeek;
- currentDay = day;
- }
- }
-
- heartbeat = heartRateController.HeartRate();
- heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
- if(heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) {
- char heartbeatBuffer[4];
- if(heartbeatRunning.Get())
- sprintf(heartbeatBuffer, "%d", heartbeat.Get());
- else
- sprintf(heartbeatBuffer, "---");
-
- lv_label_set_text(heartbeatValue, heartbeatBuffer);
- lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2);
- lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
- lv_obj_align(heartbeatBpm, heartbeatValue, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
- }
-
- // TODO stepCount = stepController.GetValue();
- if(stepCount.IsUpdated()) {
- char stepBuffer[5];
- sprintf(stepBuffer, "%lu", stepCount.Get());
- lv_label_set_text(stepValue, stepBuffer);
- lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -5, -2);
- lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
- }
+bool Clock::Refresh() {
+ screens.Refresh();
return running;
}
-
-void Clock::OnObjectEvent(lv_obj_t *obj, lv_event_t event) {
- if(obj == backgroundLabel) {
- if (event == LV_EVENT_CLICKED) {
-
- running = false;
- }
- }
-}
-
bool Clock::OnButtonPushed() {
running = false;
return false;
}
+bool Clock::OnTouchEvent(Pinetime::Applications::TouchEvents event) {
+ return screens.OnTouchEvent(event);
+}
+std::unique_ptr Clock::WatchFaceDigitalScreen() {
+ return std::unique_ptr(new Screens::WatchFaceDigital(app, dateTimeController, batteryController, bleController, notificatioManager, settingsController, heartRateController));
+}
+
+std::unique_ptr Clock::WatchFaceAnalogScreen() {
+ return std::unique_ptr(new Screens::WatchFaceAnalog(app, dateTimeController, batteryController, bleController, notificatioManager, settingsController));
+}
+
+/*
+// Examples for more watch faces
+std::unique_ptr Clock::WatchFaceMinimalScreen() {
+ return std::unique_ptr(new Screens::WatchFaceMinimal(app, dateTimeController, batteryController, bleController, notificatioManager, settingsController));
+}
+
+std::unique_ptr Clock::WatchFaceCustomScreen() {
+ return std::unique_ptr(new Screens::WatchFaceCustom(app, dateTimeController, batteryController, bleController, notificatioManager, settingsController));
+}
+*/
\ No newline at end of file
diff --git a/src/displayapp/screens/Clock.h b/src/displayapp/screens/Clock.h
index 5e9cfeaa..964ccbf6 100644
--- a/src/displayapp/screens/Clock.h
+++ b/src/displayapp/screens/Clock.h
@@ -5,38 +5,22 @@
#include
#include
#include "Screen.h"
+#include "ScreenList.h"
#include "components/datetime/DateTimeController.h"
namespace Pinetime {
+ namespace Drivers {
+ class BMA421;
+ }
namespace Controllers {
+ class Settings;
class Battery;
class Ble;
class NotificationManager;
- class HeartRateController;
}
namespace Applications {
namespace Screens {
-
- template
- class DirtyValue {
- public:
- DirtyValue() = default; // Use NSDMI
- explicit DirtyValue(T const& v):value{v}{} // Use MIL and const-lvalue-ref
- bool IsUpdated() const { return isUpdated; }
- T const& Get() { this->isUpdated = false; return value; } // never expose a non-const lvalue-ref
- DirtyValue& operator=(const T& other) {
- if (this->value != other) {
- this->value = other;
- this->isUpdated = true;
- }
- return *this;
- }
- private:
- T value{}; // NSDMI - default initialise type
- bool isUpdated{true}; // NSDMI - use brace initilisation
- };
-
class Clock : public Screen {
public:
Clock(DisplayApp* app,
@@ -44,48 +28,32 @@ namespace Pinetime {
Controllers::Battery& batteryController,
Controllers::Ble& bleController,
Controllers::NotificationManager& notificatioManager,
+ Controllers::Settings &settingsController,
Controllers::HeartRateController& heartRateController);
~Clock() override;
bool Refresh() override;
bool OnButtonPushed() override;
+ bool OnTouchEvent(TouchEvents event) override;
- void OnObjectEvent(lv_obj_t *pObj, lv_event_t i);
private:
- char displayedChar[5];
-
- uint16_t currentYear = 1970;
- Pinetime::Controllers::DateTime::Months currentMonth = Pinetime::Controllers::DateTime::Months::Unknown;
- Pinetime::Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown;
- uint8_t currentDay = 0;
-
- DirtyValue batteryPercentRemaining {};
- DirtyValue bleState {};
- DirtyValue> currentDateTime{};
- DirtyValue stepCount {};
- DirtyValue heartbeat {};
- DirtyValue heartbeatRunning {};
- DirtyValue notificationState {};
-
- lv_obj_t* label_time;
- lv_obj_t* label_date;
- lv_obj_t* backgroundLabel;
- lv_obj_t* batteryIcon;
- lv_obj_t* bleIcon;
- lv_obj_t* batteryPlug;
- lv_obj_t* heartbeatIcon;
- lv_obj_t* heartbeatValue;
- lv_obj_t* heartbeatBpm;
- lv_obj_t* stepIcon;
- lv_obj_t* stepValue;
- lv_obj_t* notificationIcon;
Controllers::DateTime& dateTimeController;
Controllers::Battery& batteryController;
Controllers::Ble& bleController;
Controllers::NotificationManager& notificatioManager;
+ Controllers::Settings& settingsController;
Controllers::HeartRateController& heartRateController;
+
+ ScreenList<2> screens;
+ std::unique_ptr WatchFaceDigitalScreen();
+ std::unique_ptr WatchFaceAnalogScreen();
+
+ // Examples for more watch faces
+ //std::unique_ptr WatchFaceMinimalScreen();
+ //std::unique_ptr WatchFaceCustomScreen();
+
bool running = true;
};
diff --git a/src/displayapp/screens/Screen.h b/src/displayapp/screens/Screen.h
index 6b1d0eec..638dac99 100644
--- a/src/displayapp/screens/Screen.h
+++ b/src/displayapp/screens/Screen.h
@@ -7,6 +7,26 @@ namespace Pinetime {
namespace Applications {
class DisplayApp;
namespace Screens {
+
+ template
+ class DirtyValue {
+ public:
+ DirtyValue() = default; // Use NSDMI
+ explicit DirtyValue(T const& v):value{v}{} // Use MIL and const-lvalue-ref
+ bool IsUpdated() const { return isUpdated; }
+ T const& Get() { this->isUpdated = false; return value; } // never expose a non-const lvalue-ref
+ DirtyValue& operator=(const T& other) {
+ if (this->value != other) {
+ this->value = other;
+ this->isUpdated = true;
+ }
+ return *this;
+ }
+ private:
+ T value{}; // NSDMI - default initialise type
+ bool isUpdated{true}; // NSDMI - use brace initilisation
+ };
+
class Screen {
public:
explicit Screen(DisplayApp* app) : app{app} {}
diff --git a/src/displayapp/screens/ScreenList.h b/src/displayapp/screens/ScreenList.h
index 736e3634..43b33f40 100644
--- a/src/displayapp/screens/ScreenList.h
+++ b/src/displayapp/screens/ScreenList.h
@@ -9,16 +9,19 @@
namespace Pinetime {
namespace Applications {
namespace Screens {
+
+ enum class ScreenListModes {UpDown, RightLeft, LongPress};
template
class ScreenList : public Screen {
public:
- ScreenList(DisplayApp* app, std::array()>, N>&& screens)
- : Screen(app), screens{std::move(screens)}, current{this->screens[0]()} {
+ ScreenList(DisplayApp* app, uint8_t initScreen, std::array()>, N>&& screens, ScreenListModes mode)
+ : Screen(app), initScreen{initScreen}, screens{std::move(screens)}, mode{mode}, current{this->screens[initScreen]()} {
+ screenIndex = initScreen;
}
~ScreenList() override {
-
+ lv_obj_clean(lv_scr_act());
}
bool Refresh() override {
@@ -32,34 +35,80 @@ namespace Pinetime {
}
bool OnTouchEvent(TouchEvents event) override {
- switch (event) {
- case TouchEvents::SwipeDown:
- if (screenIndex > 0) {
- current.reset(nullptr);
- app->SetFullRefresh(DisplayApp::FullRefreshDirections::Down);
- screenIndex--;
- current = screens[screenIndex]();
- }
- return true;
- case TouchEvents::SwipeUp:
- if (screenIndex < screens.size() - 1) {
- current.reset(nullptr);
- app->SetFullRefresh(DisplayApp::FullRefreshDirections::Up);
- screenIndex++;
- current = screens[screenIndex]();
- }
- return true;
- default:
- return false;
+
+ if ( mode == ScreenListModes::UpDown) {
+ switch (event) {
+ case TouchEvents::SwipeDown:
+ if (screenIndex > 0) {
+ current.reset(nullptr);
+ app->SetFullRefresh(DisplayApp::FullRefreshDirections::Down);
+ screenIndex--;
+ current = screens[screenIndex]();
+ return true;
+ } else {
+ return false;
+ }
+
+ case TouchEvents::SwipeUp:
+ if (screenIndex < screens.size() - 1) {
+ current.reset(nullptr);
+ app->SetFullRefresh(DisplayApp::FullRefreshDirections::Up);
+ screenIndex++;
+ current = screens[screenIndex]();
+ }
+ return true;
+ default:
+ return false;
+ }
+ } else if ( mode == ScreenListModes::RightLeft) {
+ switch (event) {
+ case TouchEvents::SwipeRight:
+ if (screenIndex > 0) {
+ current.reset(nullptr);
+ app->SetFullRefresh(DisplayApp::FullRefreshDirections::None);
+ screenIndex--;
+ current = screens[screenIndex]();
+ return true;
+ } else {
+ return false;
+ }
+
+ case TouchEvents::SwipeLeft:
+ if (screenIndex < screens.size() - 1) {
+ current.reset(nullptr);
+ app->SetFullRefresh(DisplayApp::FullRefreshDirections::None);
+ screenIndex++;
+ current = screens[screenIndex]();
+ }
+ return true;
+ default:
+ return false;
+ }
+ } else if ( event == TouchEvents::LongTap ) {
+ if (screenIndex < screens.size() - 1) {
+ screenIndex++;
+ } else {
+ screenIndex = 0;
+ }
+ current.reset(nullptr);
+ app->SetFullRefresh(DisplayApp::FullRefreshDirections::None);
+ current = screens[screenIndex]();
+ return true;
}
+
return false;
}
private:
- bool running = true;
- uint8_t screenIndex = 0;
+
+ uint8_t initScreen = 0;
std::array()>, N> screens;
+ ScreenListModes mode = ScreenListModes::UpDown;
+
+ uint8_t screenIndex = 0;
std::unique_ptr current;
+
+ bool running = true;
};
}
}
diff --git a/src/displayapp/screens/SystemInfo.cpp b/src/displayapp/screens/SystemInfo.cpp
index 2de5dada..0d6f8e5a 100644
--- a/src/displayapp/screens/SystemInfo.cpp
+++ b/src/displayapp/screens/SystemInfo.cpp
@@ -20,11 +20,14 @@ SystemInfo::SystemInfo(Pinetime::Applications::DisplayApp *app,
Screen(app),
dateTimeController{dateTimeController}, batteryController{batteryController},
brightnessController{brightnessController}, bleController{bleController}, watchdog{watchdog},
- screens{app, {
+ screens{app,
+ 0,
+ {
[this]() -> std::unique_ptr { return CreateScreen1(); },
[this]() -> std::unique_ptr { return CreateScreen2(); },
[this]() -> std::unique_ptr { return CreateScreen3(); }
- }
+ },
+ Screens::ScreenListModes::UpDown
} {}
@@ -119,6 +122,6 @@ std::unique_ptr SystemInfo::CreateScreen3() {
"Public License v3\n"
"Source code:\n"
"https://github.com/\n"
- " JF002/Pinetime");
+ " JF002/InfiniTime");
return std::unique_ptr(new Screens::Label(app, t3));
}
diff --git a/src/displayapp/screens/Tile.cpp b/src/displayapp/screens/Tile.cpp
index 3b82b060..ca753db9 100644
--- a/src/displayapp/screens/Tile.cpp
+++ b/src/displayapp/screens/Tile.cpp
@@ -10,7 +10,10 @@ static void event_handler(lv_obj_t * obj, lv_event_t event) {
screen->OnObjectEvent(obj, event, eventData);
}
-Tile::Tile(DisplayApp* app, std::array& applications) : Screen(app) {
+Tile::Tile(uint8_t screenID, DisplayApp* app, Controllers::Settings& settingsController, std::array& applications) : Screen(app) {
+
+ settingsController.SetAppMenu(screenID);
+
for(int i = 0, appIndex = 0; i < 8; i++) {
if(i == 3) btnm_map1[i] = "\n";
else if(i == 7) btnm_map1[i] = "";
diff --git a/src/displayapp/screens/Tile.h b/src/displayapp/screens/Tile.h
index 55ed45e3..f717a220 100644
--- a/src/displayapp/screens/Tile.h
+++ b/src/displayapp/screens/Tile.h
@@ -5,6 +5,7 @@
#include
#include "Screen.h"
#include "../Apps.h"
+#include "components/settings/Settings.h"
namespace Pinetime {
namespace Applications {
@@ -16,7 +17,7 @@ namespace Pinetime {
Pinetime::Applications::Apps application;
};
- explicit Tile(DisplayApp* app, std::array& applications);
+ explicit Tile(uint8_t screenID, DisplayApp* app, Controllers::Settings& settingsController, std::array& applications);
~Tile() override;
bool Refresh() override;
diff --git a/src/displayapp/screens/WatchFaceAnalog.cpp b/src/displayapp/screens/WatchFaceAnalog.cpp
new file mode 100644
index 00000000..b51d48c7
--- /dev/null
+++ b/src/displayapp/screens/WatchFaceAnalog.cpp
@@ -0,0 +1,208 @@
+#include
+#include "WatchFaceAnalog.h"
+#include "BatteryIcon.h"
+#include "BleIcon.h"
+#include "Symbols.h"
+#include "NotificationIcon.h"
+
+LV_IMG_DECLARE(bg_clock);
+
+using namespace Pinetime::Applications::Screens;
+
+#define HOUR_LENGTH 70
+#define MINUTE_LENGTH 90
+#define SECOND_LENGTH 110
+#define PI 3.14159265358979323846
+
+// ##
+static int16_t coordinate_x_relocate(int16_t x)
+{
+ return ((x) + LV_HOR_RES / 2);
+}
+
+// ##
+static int16_t coordinate_y_relocate(int16_t y)
+{
+ return (((y) - LV_HOR_RES / 2) < 0) ? (0 - ((y) - LV_HOR_RES / 2)) : ((y) - LV_HOR_RES / 2);
+}
+
+WatchFaceAnalog::WatchFaceAnalog(Pinetime::Applications::DisplayApp *app,
+ Controllers::DateTime& dateTimeController,
+ Controllers::Battery& batteryController,
+ Controllers::Ble& bleController,
+ Controllers::NotificationManager& notificatioManager,
+ Controllers::Settings &settingsController) : Screen(app), currentDateTime{{}},
+ dateTimeController{dateTimeController}, batteryController{batteryController},
+ bleController{bleController}, notificatioManager{notificatioManager},
+ settingsController{settingsController} {
+ settingsController.SetClockFace(1);
+
+ sHour = 99;
+ sMinute = 99;
+ sSecond = 99;
+
+ lv_obj_t * bg_clock_img = lv_img_create(lv_scr_act(), NULL);
+ lv_img_set_src(bg_clock_img, &bg_clock);
+ lv_obj_align(bg_clock_img, NULL, LV_ALIGN_CENTER, 0, 0);
+
+ batteryIcon = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text(batteryIcon, Symbols::batteryHalf);
+ lv_obj_align(batteryIcon, NULL, LV_ALIGN_IN_BOTTOM_RIGHT, -8, -4);
+
+
+ notificationIcon = lv_label_create(lv_scr_act(), NULL);
+ lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FF00));
+ lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
+ lv_obj_align(notificationIcon, NULL, LV_ALIGN_IN_BOTTOM_LEFT, 8, -4);
+
+ // Date - Day / Week day
+
+ label_date_day = lv_label_create(lv_scr_act(), NULL);
+ lv_obj_set_style_local_text_color(label_date_day, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xf0a500));
+ lv_label_set_text_fmt(label_date_day, "%s\n%02i", dateTimeController.DayOfWeekShortToString(), dateTimeController.Day());
+ lv_label_set_align( label_date_day, LV_LABEL_ALIGN_CENTER );
+ lv_obj_align(label_date_day, NULL, LV_ALIGN_CENTER, 50, 0);
+
+ minute_body = lv_line_create(lv_scr_act(), NULL);
+ minute_body_trace = lv_line_create(lv_scr_act(), NULL);
+ hour_body = lv_line_create(lv_scr_act(), NULL);
+ hour_body_trace = lv_line_create(lv_scr_act(), NULL);
+ second_body = lv_line_create(lv_scr_act(), NULL);
+
+
+ lv_style_init(&second_line_style);
+ lv_style_set_line_width(&second_line_style, LV_STATE_DEFAULT, 3);
+ lv_style_set_line_color(&second_line_style, LV_STATE_DEFAULT, LV_COLOR_RED);
+ lv_style_set_line_rounded(&second_line_style, LV_STATE_DEFAULT, true);
+ lv_obj_add_style(second_body, LV_LINE_PART_MAIN, &second_line_style);
+
+ lv_style_init(&minute_line_style);
+ lv_style_set_line_width(&minute_line_style, LV_STATE_DEFAULT, 7);
+ lv_style_set_line_color(&minute_line_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);
+ lv_style_set_line_rounded(&minute_line_style, LV_STATE_DEFAULT, true);
+ lv_obj_add_style(minute_body, LV_LINE_PART_MAIN, &minute_line_style);
+
+ lv_style_init(&minute_line_style_trace);
+ lv_style_set_line_width(&minute_line_style_trace, LV_STATE_DEFAULT, 3);
+ lv_style_set_line_color(&minute_line_style_trace, LV_STATE_DEFAULT, LV_COLOR_WHITE);
+ lv_style_set_line_rounded(&minute_line_style_trace, LV_STATE_DEFAULT, false);
+ lv_obj_add_style(minute_body_trace, LV_LINE_PART_MAIN, &minute_line_style_trace);
+
+
+ lv_style_init(&hour_line_style);
+ lv_style_set_line_width(&hour_line_style, LV_STATE_DEFAULT, 7);
+ lv_style_set_line_color(&hour_line_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);
+ lv_style_set_line_rounded(&hour_line_style, LV_STATE_DEFAULT, true);
+ lv_obj_add_style(hour_body, LV_LINE_PART_MAIN, &hour_line_style);
+
+ lv_style_init(&hour_line_style_trace);
+ lv_style_set_line_width(&hour_line_style_trace, LV_STATE_DEFAULT, 3);
+ lv_style_set_line_color(&hour_line_style_trace, LV_STATE_DEFAULT, LV_COLOR_WHITE);
+ lv_style_set_line_rounded(&hour_line_style_trace, LV_STATE_DEFAULT, false);
+ lv_obj_add_style(hour_body_trace, LV_LINE_PART_MAIN, &hour_line_style_trace);
+
+ UpdateClock();
+
+}
+
+WatchFaceAnalog::~WatchFaceAnalog() {
+
+ lv_style_reset(&hour_line_style);
+ lv_style_reset(&hour_line_style_trace);
+ lv_style_reset(&minute_line_style);
+ lv_style_reset(&minute_line_style_trace);
+ lv_style_reset(&second_line_style);
+
+ lv_obj_clean(lv_scr_act());
+}
+
+void WatchFaceAnalog::UpdateClock() {
+
+ hour = dateTimeController.Hours();
+ minute = dateTimeController.Minutes();
+ second = dateTimeController.Seconds();
+
+ if(sMinute != minute) {
+ minute_point[0].x = coordinate_x_relocate(30 * sin(minute * 6 * PI / 180));
+ minute_point[0].y = coordinate_y_relocate(30 * cos(minute * 6 * PI / 180));
+ minute_point[1].x = coordinate_x_relocate(MINUTE_LENGTH * sin(minute * 6 * PI / 180));
+ minute_point[1].y = coordinate_y_relocate(MINUTE_LENGTH * cos(minute * 6 * PI / 180));
+
+ minute_point_trace[0].x = coordinate_x_relocate(5 * sin(minute * 6 * PI / 180));
+ minute_point_trace[0].y = coordinate_y_relocate(5 * cos(minute * 6 * PI / 180));
+ minute_point_trace[1].x = coordinate_x_relocate(31 * sin(minute * 6 * PI / 180));
+ minute_point_trace[1].y = coordinate_y_relocate(31 * cos(minute * 6 * PI / 180));
+
+ lv_line_set_points(minute_body, minute_point, 2);
+ lv_line_set_points(minute_body_trace, minute_point_trace, 2);
+ }
+
+ if(sHour != hour || sMinute != minute) {
+ sHour = hour;
+ sMinute = minute;
+ hour_point[0].x = coordinate_x_relocate(30 * sin((((hour > 12 ? hour - 12 : hour) * 30) + (minute * 0.5)) * PI / 180));
+ hour_point[0].y = coordinate_y_relocate(30 * cos((((hour > 12 ? hour - 12 : hour) * 30) + (minute * 0.5)) * PI / 180));
+ hour_point[1].x = coordinate_x_relocate(HOUR_LENGTH * sin((((hour > 12 ? hour - 12 : hour) * 30) + (minute * 0.5)) * PI / 180));
+ hour_point[1].y = coordinate_y_relocate(HOUR_LENGTH * cos((((hour > 12 ? hour - 12 : hour) * 30) + (minute * 0.5)) * PI / 180));
+
+ hour_point_trace[0].x = coordinate_x_relocate(5 * sin((((hour > 12 ? hour - 12 : hour) * 30) + (minute * 0.5)) * PI / 180));
+ hour_point_trace[0].y = coordinate_y_relocate(5 * cos((((hour > 12 ? hour - 12 : hour) * 30) + (minute * 0.5)) * PI / 180));
+ hour_point_trace[1].x = coordinate_x_relocate(31 * sin((((hour > 12 ? hour - 12 : hour) * 30) + (minute * 0.5)) * PI / 180));
+ hour_point_trace[1].y = coordinate_y_relocate(31 * cos((((hour > 12 ? hour - 12 : hour) * 30) + (minute * 0.5)) * PI / 180));
+
+ lv_line_set_points(hour_body, hour_point, 2);
+ lv_line_set_points(hour_body_trace, hour_point_trace, 2);
+ }
+
+ if(sSecond != second) {
+ sSecond = second;
+ second_point[0].x = coordinate_x_relocate(20 * sin((180 + second * 6) * PI / 180));
+ second_point[0].y = coordinate_y_relocate(20 * cos((180 + second * 6) * PI / 180));
+ second_point[1].x = coordinate_x_relocate(SECOND_LENGTH * sin(second * 6 * PI / 180));
+ second_point[1].y = coordinate_y_relocate(SECOND_LENGTH * cos(second * 6 * PI / 180));
+ lv_line_set_points(second_body, second_point, 2);
+
+ }
+}
+
+
+bool WatchFaceAnalog::Refresh() {
+
+ batteryPercentRemaining = batteryController.PercentRemaining();
+ if (batteryPercentRemaining.IsUpdated()) {
+ auto batteryPercent = batteryPercentRemaining.Get();
+ lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryPercent));
+ }
+
+
+ notificationState = notificatioManager.AreNewNotificationsAvailable();
+
+ if(notificationState.IsUpdated()) {
+ if(notificationState.Get() == true)
+ lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(true));
+ else
+ lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
+ }
+
+ currentDateTime = dateTimeController.CurrentDateTime();
+
+ if(currentDateTime.IsUpdated()) {
+
+ month = dateTimeController.Month();
+ day = dateTimeController.Day();
+ dayOfWeek = dateTimeController.DayOfWeek();
+
+ UpdateClock();
+
+ if ((month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) {
+
+ lv_label_set_text_fmt(label_date_day, "%s\n%02i", dateTimeController.DayOfWeekShortToString(), day);
+
+ currentMonth = month;
+ currentDayOfWeek = dayOfWeek;
+ currentDay = day;
+ }
+ }
+
+ return true;
+}
\ No newline at end of file
diff --git a/src/displayapp/screens/WatchFaceAnalog.h b/src/displayapp/screens/WatchFaceAnalog.h
new file mode 100644
index 00000000..56b086ab
--- /dev/null
+++ b/src/displayapp/screens/WatchFaceAnalog.h
@@ -0,0 +1,90 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include "Screen.h"
+#include "ScreenList.h"
+#include "components/datetime/DateTimeController.h"
+#include "components/battery/BatteryController.h"
+#include "components/ble/BleController.h"
+#include "components/ble/NotificationManager.h"
+
+namespace Pinetime {
+ namespace Controllers {
+ class Settings;
+ class Battery;
+ class Ble;
+ class NotificationManager;
+ }
+ namespace Applications {
+ namespace Screens {
+
+ class WatchFaceAnalog : public Screen {
+ public:
+ WatchFaceAnalog(DisplayApp* app,
+ Controllers::DateTime& dateTimeController,
+ Controllers::Battery& batteryController,
+ Controllers::Ble& bleController,
+ Controllers::NotificationManager& notificatioManager,
+ Controllers::Settings &settingsController);
+
+ ~WatchFaceAnalog() override;
+
+ bool Refresh() override;
+
+ private:
+ uint8_t sHour, sMinute, sSecond;
+ uint8_t hour;
+ uint8_t minute;
+ uint8_t second;
+
+ Pinetime::Controllers::DateTime::Months month;
+ uint8_t day;
+ Pinetime::Controllers::DateTime::Days dayOfWeek;
+
+ Pinetime::Controllers::DateTime::Months currentMonth = Pinetime::Controllers::DateTime::Months::Unknown;
+ Pinetime::Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown;
+ uint8_t currentDay = 0;
+
+ DirtyValue batteryPercentRemaining {0};
+ DirtyValue> currentDateTime;
+ DirtyValue notificationState {false};
+
+ lv_obj_t *hour_body;
+ lv_obj_t *hour_body_trace;
+ lv_obj_t *minute_body;
+ lv_obj_t *minute_body_trace;
+ lv_obj_t *second_body;
+
+ // ##
+ lv_point_t hour_point[2];
+ lv_point_t hour_point_trace[2];
+ lv_point_t minute_point[2];
+ lv_point_t minute_point_trace[2];
+ lv_point_t second_point[2];
+
+ // ##
+ lv_style_t hour_line_style;
+ lv_style_t hour_line_style_trace;
+ lv_style_t minute_line_style;
+ lv_style_t minute_line_style_trace;
+ lv_style_t second_line_style;
+
+ lv_obj_t* label_date_day;
+ lv_obj_t* batteryIcon;
+ lv_obj_t* notificationIcon;
+
+
+ Controllers::DateTime& dateTimeController;
+ Controllers::Battery& batteryController;
+ Controllers::Ble& bleController;
+ Controllers::NotificationManager& notificatioManager;
+ Controllers::Settings& settingsController;
+
+ void UpdateClock();
+ };
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/displayapp/screens/WatchFaceDigital.cpp b/src/displayapp/screens/WatchFaceDigital.cpp
new file mode 100644
index 00000000..c39fe496
--- /dev/null
+++ b/src/displayapp/screens/WatchFaceDigital.cpp
@@ -0,0 +1,257 @@
+#include "WatchFaceDigital.h"
+
+#include
+#include
+#include
+#include "BatteryIcon.h"
+#include "BleIcon.h"
+#include "NotificationIcon.h"
+#include "Symbols.h"
+#include "components/battery/BatteryController.h"
+#include "components/ble/BleController.h"
+#include "components/ble/NotificationManager.h"
+#include "components/heartrate/HeartRateController.h"
+#include "components/settings/Settings.h"
+#include "../DisplayApp.h"
+
+using namespace Pinetime::Applications::Screens;
+
+
+WatchFaceDigital::WatchFaceDigital(DisplayApp* app,
+ Controllers::DateTime& dateTimeController,
+ Controllers::Battery& batteryController,
+ Controllers::Ble& bleController,
+ Controllers::NotificationManager& notificatioManager,
+ Controllers::Settings &settingsController,
+ Controllers::HeartRateController& heartRateController): Screen(app), currentDateTime{{}},
+ dateTimeController{dateTimeController}, batteryController{batteryController},
+ bleController{bleController}, notificatioManager{notificatioManager},
+ settingsController{settingsController},
+ heartRateController{heartRateController} {
+ settingsController.SetClockFace(0);
+
+ displayedChar[0] = 0;
+ displayedChar[1] = 0;
+ displayedChar[2] = 0;
+ displayedChar[3] = 0;
+ displayedChar[4] = 0;
+
+ batteryIcon = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text(batteryIcon, Symbols::batteryFull);
+ lv_obj_align(batteryIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, -5, 2);
+
+ batteryPlug = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_set_style_local_text_color(batteryPlug, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xFF0000));
+ lv_label_set_text(batteryPlug, Symbols::plug);
+ lv_obj_align(batteryPlug, batteryIcon, LV_ALIGN_OUT_LEFT_MID, -5, 0);
+
+ bleIcon = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x0000FF));
+ lv_label_set_text(bleIcon, Symbols::bluetooth);
+ lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0);
+
+ notificationIcon = lv_label_create(lv_scr_act(), NULL);
+ lv_obj_set_style_local_text_color(notificationIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FF00));
+ lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
+ lv_obj_align(notificationIcon, nullptr, LV_ALIGN_IN_TOP_LEFT, 10, 0);
+
+ label_date = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60);
+ lv_obj_set_style_local_text_color(label_date, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x999999));
+
+ label_time = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_set_style_local_text_font(label_time, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_extrabold_compressed);
+
+ lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0);
+
+ label_time_ampm = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text_static(label_time_ampm, "");
+ lv_obj_align(label_time_ampm, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, -30, -55);
+
+ backgroundLabel = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_set_click(backgroundLabel, true);
+ lv_label_set_long_mode(backgroundLabel, LV_LABEL_LONG_CROP);
+ lv_obj_set_size(backgroundLabel, 240, 240);
+ lv_obj_set_pos(backgroundLabel, 0, 0);
+ lv_label_set_text(backgroundLabel, "");
+
+
+ heartbeatIcon = lv_label_create(lv_scr_act(), nullptr);
+ lv_label_set_text(heartbeatIcon, Symbols::heartBeat);
+ lv_obj_set_style_local_text_color(heartbeatIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
+ lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2);
+
+ heartbeatValue = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_set_style_local_text_color(heartbeatValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
+ lv_label_set_text(heartbeatValue, "---");
+ lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
+
+ heartbeatBpm = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_set_style_local_text_color(heartbeatBpm, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0xCE1B1B));
+ lv_label_set_text(heartbeatBpm, "BPM");
+ lv_obj_align(heartbeatBpm, heartbeatValue, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
+
+ stepValue = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_set_style_local_text_color(stepValue, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
+ lv_label_set_text(stepValue, "0");
+ lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -5, -2);
+
+ stepIcon = lv_label_create(lv_scr_act(), nullptr);
+ lv_obj_set_style_local_text_color(stepIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x00FFE7));
+ lv_label_set_text(stepIcon, Symbols::shoe);
+ lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
+}
+
+WatchFaceDigital::~WatchFaceDigital() {
+ lv_obj_clean(lv_scr_act());
+}
+
+bool WatchFaceDigital::Refresh() {
+ batteryPercentRemaining = batteryController.PercentRemaining();
+ if (batteryPercentRemaining.IsUpdated()) {
+ auto batteryPercent = batteryPercentRemaining.Get();
+ lv_label_set_text(batteryIcon, BatteryIcon::GetBatteryIcon(batteryPercent));
+ auto isCharging = batteryController.IsCharging() || batteryController.IsPowerPresent();
+ lv_label_set_text(batteryPlug, BatteryIcon::GetPlugIcon(isCharging));
+ }
+
+ bleState = bleController.IsConnected();
+ if (bleState.IsUpdated()) {
+ if(bleState.Get() == true) {
+ lv_label_set_text(bleIcon, BleIcon::GetIcon(true));
+ } else {
+ lv_label_set_text(bleIcon, BleIcon::GetIcon(false));
+ }
+ }
+ lv_obj_align(batteryIcon, lv_scr_act(), LV_ALIGN_IN_TOP_RIGHT, -5, 5);
+ lv_obj_align(batteryPlug, batteryIcon, LV_ALIGN_OUT_LEFT_MID, -5, 0);
+ lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0);
+
+ notificationState = notificatioManager.AreNewNotificationsAvailable();
+ if(notificationState.IsUpdated()) {
+ if(notificationState.Get() == true)
+ lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(true));
+ else
+ lv_label_set_text(notificationIcon, NotificationIcon::GetIcon(false));
+ }
+
+ currentDateTime = dateTimeController.CurrentDateTime();
+
+ if(currentDateTime.IsUpdated()) {
+ auto newDateTime = currentDateTime.Get();
+
+ auto dp = date::floor(newDateTime);
+ auto time = date::make_time(newDateTime-dp);
+ auto yearMonthDay = date::year_month_day(dp);
+
+ auto year = (int)yearMonthDay.year();
+ auto month = static_cast((unsigned)yearMonthDay.month());
+ auto day = (unsigned)yearMonthDay.day();
+ auto dayOfWeek = static_cast(date::weekday(yearMonthDay).iso_encoding());
+
+ int hour = time.hours().count();
+ auto minute = time.minutes().count();
+
+ char minutesChar[3];
+ sprintf(minutesChar, "%02d", static_cast(minute));
+
+ char hoursChar[3];
+ char ampmChar[3];
+ if ( settingsController.GetClockType() == Controllers::Settings::ClockType::H24 ) {
+ sprintf(hoursChar, "%02d", hour);
+ } else {
+ if (hour == 0 && hour != 12) {
+ hour = 12;
+ sprintf(ampmChar, "AM");
+ }
+ else if (hour == 12 && hour != 0) {
+ hour = 12;
+ sprintf(ampmChar, "PM");
+ }
+ else if (hour < 12 && hour != 0) {
+ sprintf(ampmChar, "AM");
+ }
+ else if (hour > 12 && hour != 0)
+ {
+ hour = hour - 12;
+ sprintf(ampmChar, "PM");
+ }
+ sprintf(hoursChar, "%02d", hour);
+ }
+
+ if(hoursChar[0] != displayedChar[0] || hoursChar[1] != displayedChar[1] || minutesChar[0] != displayedChar[2] || minutesChar[1] != displayedChar[3]) {
+ displayedChar[0] = hoursChar[0];
+ displayedChar[1] = hoursChar[1];
+ displayedChar[2] = minutesChar[0];
+ displayedChar[3] = minutesChar[1];
+
+ char timeStr[6];
+
+ if ( settingsController.GetClockType() == Controllers::Settings::ClockType::H12 ) {
+ lv_label_set_text(label_time_ampm, ampmChar);
+ if ( hoursChar[0] == '0' ) { hoursChar[0] = ' '; }
+ }
+
+ sprintf(timeStr, "%c%c:%c%c", hoursChar[0],hoursChar[1],minutesChar[0], minutesChar[1]);
+ lv_label_set_text(label_time, timeStr);
+
+ if ( settingsController.GetClockType() == Controllers::Settings::ClockType::H12 ) {
+ lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_IN_RIGHT_MID, 0, 0);
+ } else {
+ lv_obj_align(label_time, lv_scr_act(), LV_ALIGN_CENTER, 0, 0);
+ }
+
+ }
+
+ if ((year != currentYear) || (month != currentMonth) || (dayOfWeek != currentDayOfWeek) || (day != currentDay)) {
+ char dateStr[22];
+ if ( settingsController.GetClockType() == Controllers::Settings::ClockType::H24 ) {
+ sprintf(dateStr, "%s %d %s %d", dateTimeController.DayOfWeekShortToString(), day, dateTimeController.MonthShortToString(), year);
+ } else {
+ sprintf(dateStr, "%s %s %d %d", dateTimeController.DayOfWeekShortToString(), dateTimeController.MonthShortToString(), day, year);
+ }
+ lv_label_set_text(label_date, dateStr);
+ lv_obj_align(label_date, lv_scr_act(), LV_ALIGN_CENTER, 0, 60);
+
+
+ currentYear = year;
+ currentMonth = month;
+ currentDayOfWeek = dayOfWeek;
+ currentDay = day;
+ }
+ }
+
+ heartbeat = heartRateController.HeartRate();
+ heartbeatRunning = heartRateController.State() != Controllers::HeartRateController::States::Stopped;
+ if(heartbeat.IsUpdated() || heartbeatRunning.IsUpdated()) {
+ char heartbeatBuffer[4];
+ if(heartbeatRunning.Get())
+ sprintf(heartbeatBuffer, "%d", heartbeat.Get());
+ else
+ sprintf(heartbeatBuffer, "---");
+
+ lv_label_set_text(heartbeatValue, heartbeatBuffer);
+ lv_obj_align(heartbeatIcon, lv_scr_act(), LV_ALIGN_IN_BOTTOM_LEFT, 5, -2);
+ lv_obj_align(heartbeatValue, heartbeatIcon, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
+ lv_obj_align(heartbeatBpm, heartbeatValue, LV_ALIGN_OUT_RIGHT_MID, 5, 0);
+ }
+
+ // TODO stepCount = stepController.GetValue();
+ if(stepCount.IsUpdated()) {
+ char stepBuffer[5];
+ sprintf(stepBuffer, "%lu", stepCount.Get());
+ lv_label_set_text(stepValue, stepBuffer);
+ lv_obj_align(stepValue, lv_scr_act(), LV_ALIGN_IN_BOTTOM_RIGHT, -5, -2);
+ lv_obj_align(stepIcon, stepValue, LV_ALIGN_OUT_LEFT_MID, -5, 0);
+ }
+
+ return running;
+}
+
+
+bool WatchFaceDigital::OnButtonPushed() {
+ running = false;
+ return false;
+}
+
+
diff --git a/src/displayapp/screens/WatchFaceDigital.h b/src/displayapp/screens/WatchFaceDigital.h
new file mode 100644
index 00000000..70a9ce5d
--- /dev/null
+++ b/src/displayapp/screens/WatchFaceDigital.h
@@ -0,0 +1,82 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+#include "Screen.h"
+#include "ScreenList.h"
+#include "components/datetime/DateTimeController.h"
+
+namespace Pinetime {
+ namespace Controllers {
+ class Settings;
+ class Battery;
+ class Ble;
+ class NotificationManager;
+ class HeartRateController;
+ }
+
+ namespace Applications {
+ namespace Screens {
+
+ class WatchFaceDigital : public Screen {
+ public:
+ WatchFaceDigital(DisplayApp* app,
+ Controllers::DateTime& dateTimeController,
+ Controllers::Battery& batteryController,
+ Controllers::Ble& bleController,
+ Controllers::NotificationManager& notificatioManager,
+ Controllers::Settings &settingsController,
+ Controllers::HeartRateController& heartRateController);
+ ~WatchFaceDigital() override;
+
+ bool Refresh() override;
+ bool OnButtonPushed() override;
+
+ void OnObjectEvent(lv_obj_t *pObj, lv_event_t i);
+ private:
+
+
+ char displayedChar[5];
+
+ uint16_t currentYear = 1970;
+ Pinetime::Controllers::DateTime::Months currentMonth = Pinetime::Controllers::DateTime::Months::Unknown;
+ Pinetime::Controllers::DateTime::Days currentDayOfWeek = Pinetime::Controllers::DateTime::Days::Unknown;
+ uint8_t currentDay = 0;
+
+ DirtyValue batteryPercentRemaining {};
+ DirtyValue bleState {};
+ DirtyValue> currentDateTime{};
+ DirtyValue stepCount {};
+ DirtyValue heartbeat {};
+ DirtyValue heartbeatRunning {};
+ DirtyValue notificationState {};
+
+ lv_obj_t* label_time;
+ lv_obj_t* label_time_ampm;
+ lv_obj_t* label_date;
+ lv_obj_t* backgroundLabel;
+ lv_obj_t* batteryIcon;
+ lv_obj_t* bleIcon;
+ lv_obj_t* batteryPlug;
+ lv_obj_t* heartbeatIcon;
+ lv_obj_t* heartbeatValue;
+ lv_obj_t* heartbeatBpm;
+ lv_obj_t* stepIcon;
+ lv_obj_t* stepValue;
+ lv_obj_t* notificationIcon;
+
+ Controllers::DateTime& dateTimeController;
+ Controllers::Battery& batteryController;
+ Controllers::Ble& bleController;
+ Controllers::NotificationManager& notificatioManager;
+ Controllers::Settings& settingsController;
+ Controllers::HeartRateController& heartRateController;
+
+ bool running = true;
+
+ };
+ }
+ }
+}
diff --git a/src/drivers/St7789.cpp b/src/drivers/St7789.cpp
index 2df2c531..87cbb639 100644
--- a/src/drivers/St7789.cpp
+++ b/src/drivers/St7789.cpp
@@ -153,16 +153,9 @@ void St7789::DrawPixel(uint16_t x, uint16_t y, uint32_t color) {
WriteSpi(reinterpret_cast(&color), 2);
}
-void St7789::BeginDrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height) {
- if((x >= Width) || (y >= Height)) return;
- if((x + width - 1) >= Width) width = Width - x;
- if((y + height - 1) >= Height) height = Height - y;
-
- SetAddrWindow(0+x, ST7789_ROW_OFFSET+y, x+width-1, y+height-1);
+void St7789::DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t *data, size_t size) {
+ SetAddrWindow(x, y, x + width - 1, y + height - 1);
nrf_gpio_pin_set(pinDataCommand);
-}
-
-void St7789::NextDrawBuffer(const uint8_t *data, size_t size) {
WriteSpi(data, size);
}
diff --git a/src/drivers/St7789.h b/src/drivers/St7789.h
index a487a952..2c6f9b6b 100644
--- a/src/drivers/St7789.h
+++ b/src/drivers/St7789.h
@@ -20,9 +20,7 @@ namespace Pinetime {
void VerticalScrollDefinition(uint16_t topFixedLines, uint16_t scrollLines, uint16_t bottomFixedLines);
void VerticalScrollStartAddress(uint16_t line);
-
- void BeginDrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height);
- void NextDrawBuffer(const uint8_t* data, size_t size);
+ void DrawBuffer(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint8_t *data, size_t size);
void DisplayOn();
void DisplayOff();
diff --git a/src/main.cpp b/src/main.cpp
index fe177d0d..b29aac71 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -32,8 +32,7 @@
#include "components/ble/NotificationManager.h"
#include "components/motor/MotorController.h"
#include "components/datetime/DateTimeController.h"
-#include "displayapp/DisplayApp.h"
-#include "displayapp/LittleVgl.h"
+#include "components/settings/Settings.h"
#include "drivers/Spi.h"
#include "drivers/SpiMaster.h"
#include "drivers/SpiNorFlash.h"
@@ -85,7 +84,18 @@ Pinetime::Drivers::TwiMaster twiMaster{Pinetime::Drivers::TwiMaster::Modules::TW
Pinetime::Drivers::TwiMaster::Parameters {
MaxTwiFrequencyWithoutHardwareBug, pinTwiSda, pinTwiScl}};
Pinetime::Drivers::Cst816S touchPanel {twiMaster, touchPanelTwiAddress};
+#ifdef PINETIME_IS_RECOVERY
+static constexpr bool isFactory = true;
+#include "displayapp/DummyLittleVgl.h"
+#include "displayapp/DisplayAppRecovery.h"
Pinetime::Components::LittleVgl lvgl {lcd, touchPanel};
+#else
+static constexpr bool isFactory = false;
+#include "displayapp/LittleVgl.h"
+#include "displayapp/DisplayApp.h"
+Pinetime::Components::LittleVgl lvgl {lcd, touchPanel};
+#endif
+
Pinetime::Drivers::Hrs3300 heartRateSensor {twiMaster, heartRateSensorTwiAddress};
@@ -101,6 +111,8 @@ std::unique_ptr systemTask;
Pinetime::Controllers::MotorController motorController;
+Pinetime::Controllers::Settings settingsController;
+
void nrfx_gpiote_evt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) {
if(pin == pinTouchIrq) {
systemTask->OnTouchEvent();
@@ -114,7 +126,8 @@ void nrfx_gpiote_evt_handler(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action
extern "C" {
void vApplicationIdleHook(void) {
- lv_tick_inc(1);
+ if(!isFactory)
+ lv_tick_inc(1);
}
}
@@ -242,7 +255,7 @@ int main(void) {
debounceTimer = xTimerCreate ("debounceTimer", 200, pdFALSE, (void *) 0, DebounceTimerCallback);
systemTask.reset(new Pinetime::System::SystemTask(spi, lcd, spiNorFlash, twiMaster, touchPanel, lvgl, batteryController, bleController,
- dateTimeController, motorController, heartRateSensor));
+ dateTimeController, motorController, heartRateSensor, settingsController));
systemTask->Start();
nimble_port_init();
diff --git a/src/graphics.cpp b/src/recoveryLoader.cpp
similarity index 52%
rename from src/graphics.cpp
rename to src/recoveryLoader.cpp
index 288b5e9a..40cd66da 100644
--- a/src/graphics.cpp
+++ b/src/recoveryLoader.cpp
@@ -4,7 +4,6 @@
#include
#include
#include
-#include "bootloader/boot_graphics.h"
#include
#include
#include
@@ -14,6 +13,12 @@
#include
#include
#include
+#include
+#include "recoveryImage.h"
+
+#include "displayapp/icons/infinitime/infinitime-nb.c"
+#include "components/rle/RleDecoder.h"
+
#if NRF_LOG_ENABLED
#include "logging/NrfLogger.h"
@@ -30,14 +35,21 @@ static constexpr uint8_t pinSpiFlashCsn = 5;
static constexpr uint8_t pinLcdCsn = 25;
static constexpr uint8_t pinLcdDataCommand = 18;
+static constexpr uint8_t displayWidth = 240;
+static constexpr uint8_t displayHeight = 240;
+static constexpr uint8_t bytesPerPixel = 2;
+
+static constexpr uint16_t colorWhite = 0xFFFF;
+static constexpr uint16_t colorGreen = 0xE007;
+
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,
- pinSpiSck,
- pinSpiMosi,
- pinSpiMiso
- }
+ Pinetime::Drivers::SpiMaster::BitOrder::Msb_Lsb,
+ Pinetime::Drivers::SpiMaster::Modes::Mode3,
+ Pinetime::Drivers::SpiMaster::Frequencies::Freq8Mhz,
+ pinSpiSck,
+ pinSpiMosi,
+ pinSpiMiso
+}
};
Pinetime::Drivers::Spi flashSpi{spi, pinSpiFlashCsn};
Pinetime::Drivers::SpiNorFlash spiNorFlash{flashSpi};
@@ -48,6 +60,10 @@ Pinetime::Drivers::St7789 lcd {lcdSpi, pinLcdDataCommand};
Pinetime::Components::Gfx gfx{lcd};
Pinetime::Controllers::BrightnessController brightnessController;
+void DisplayProgressBar(uint8_t percent, uint16_t color);
+
+void DisplayLogo();
+
extern "C" {
void vApplicationIdleHook(void) {
@@ -70,10 +86,13 @@ void SPIM0_SPIS0_TWIM0_TWIS0_SPI0_TWI0_IRQHandler(void) {
}
}
-void Process(void* instance) {
- // Wait before erasing the memory to let the time to the SWD debugger to flash a new firmware before running this one.
- vTaskDelay(5000);
+void RefreshWatchdog() {
+ NRF_WDT->RR[0] = WDT_RR_RR_Reload;
+}
+uint8_t displayBuffer[displayWidth * bytesPerPixel];
+void Process(void* instance) {
+ RefreshWatchdog();
APP_GPIOTE_INIT(2);
NRF_LOG_INFO("Init...");
@@ -83,45 +102,57 @@ void Process(void* instance) {
brightnessController.Init();
lcd.Init();
gfx.Init();
- NRF_LOG_INFO("Init Done!")
+
+ NRF_LOG_INFO("Display logo")
+ DisplayLogo();
NRF_LOG_INFO("Erasing...");
- for (uint32_t erased = 0; erased < graphicSize; erased += 0x1000) {
+ for (uint32_t erased = 0; erased < sizeof(recoveryImage); erased += 0x1000) {
spiNorFlash.SectorErase(erased);
+ RefreshWatchdog();
}
- NRF_LOG_INFO("Erase done!");
- NRF_LOG_INFO("Writing graphic...");
+ NRF_LOG_INFO("Writing factory image...");
static constexpr uint32_t memoryChunkSize = 200;
uint8_t writeBuffer[memoryChunkSize];
- for(int offset = 0; offset < 115200; offset+=memoryChunkSize) {
- std::memcpy(writeBuffer, &graphicBuffer[offset], memoryChunkSize);
+ for(size_t offset = 0; offset < sizeof(recoveryImage); offset+=memoryChunkSize) {
+ std::memcpy(writeBuffer, &recoveryImage[offset], memoryChunkSize);
spiNorFlash.Write(offset, writeBuffer, memoryChunkSize);
+ DisplayProgressBar((static_cast(offset) / static_cast(sizeof(recoveryImage))) * 100.0f, colorWhite);
+ RefreshWatchdog();
}
- NRF_LOG_INFO("Writing graphic done!");
-
- NRF_LOG_INFO("Read memory and display the graphic...");
- static constexpr uint32_t screenWidth = 240;
- static constexpr uint32_t screenWidthInBytes = screenWidth*2; // LCD display 16bits color (1 pixel = 2 bytes)
- uint16_t displayLineBuffer[screenWidth];
- for(uint32_t line = 0; line < screenWidth; line++) {
- spiNorFlash.Read(line*screenWidthInBytes, reinterpret_cast(displayLineBuffer), screenWidth);
- spiNorFlash.Read((line*screenWidthInBytes)+screenWidth, reinterpret_cast(displayLineBuffer) + screenWidth, screenWidth);
- for(uint32_t col = 0; col < screenWidth; col++) {
- gfx.pixel_draw(col, line, displayLineBuffer[col]);
- }
- }
-
- NRF_LOG_INFO("Done!");
+ NRF_LOG_INFO("Writing factory image done!");
+ DisplayProgressBar(100.0f, colorGreen);
while(1) {
asm("nop" );
}
}
+void DisplayLogo() {
+ Pinetime::Tools::RleDecoder rleDecoder(infinitime_nb, sizeof(infinitime_nb));
+ for(int i = 0; i < displayWidth; i++) {
+ rleDecoder.DecodeNext(displayBuffer, displayWidth * bytesPerPixel);
+ ulTaskNotifyTake(pdTRUE, 500);
+ lcd.BeginDrawBuffer(0, i, displayWidth, 1);
+ lcd.NextDrawBuffer(reinterpret_cast(displayBuffer), displayWidth * bytesPerPixel);
+ }
+}
+
+void DisplayProgressBar(uint8_t percent, uint16_t color) {
+ static constexpr uint8_t barHeight = 20;
+ std::fill(displayBuffer, displayBuffer+(displayWidth * bytesPerPixel), color);
+ for(int i = 0; i < barHeight; i++) {
+ ulTaskNotifyTake(pdTRUE, 500);
+ uint16_t barWidth = std::min(static_cast(percent) * 2.4f, static_cast(displayWidth));
+ lcd.BeginDrawBuffer(0, displayWidth - barHeight + i, barWidth, 1);
+ lcd.NextDrawBuffer(reinterpret_cast(displayBuffer), barWidth * bytesPerPixel);
+ }
+}
+
int main(void) {
TaskHandle_t taskHandle;
-
+ RefreshWatchdog();
logger.Init();
nrf_drv_clock_init();
diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp
index 6e6360a4..19a416ba 100644
--- a/src/systemtask/SystemTask.cpp
+++ b/src/systemtask/SystemTask.cpp
@@ -14,7 +14,6 @@
#include "BootloaderVersion.h"
#include "components/ble/BleController.h"
-#include "displayapp/LittleVgl.h"
#include "drivers/Cst816s.h"
#include "drivers/St7789.h"
#include "drivers/InternalFlash.h"
@@ -41,13 +40,15 @@ SystemTask::SystemTask(Drivers::SpiMaster &spi, Drivers::St7789 &lcd,
Controllers::Battery &batteryController, Controllers::Ble &bleController,
Controllers::DateTime &dateTimeController,
Pinetime::Controllers::MotorController& motorController,
- Pinetime::Drivers::Hrs3300& heartRateSensor) :
+ Pinetime::Drivers::Hrs3300& heartRateSensor,
+ Controllers::Settings &settingsController) :
spi{spi}, lcd{lcd}, spiNorFlash{spiNorFlash},
twiMaster{twiMaster}, touchPanel{touchPanel}, lvgl{lvgl}, batteryController{batteryController},
heartRateController{*this},
bleController{bleController}, dateTimeController{dateTimeController},
watchdog{}, watchdogView{watchdog},
motorController{motorController}, heartRateSensor{heartRateSensor},
+ settingsController{settingsController},
nimbleController(*this, bleController,dateTimeController, notificationManager, batteryController, spiNorFlash, heartRateController) {
systemTasksMsgQueue = xQueueCreate(10, 1);
}
@@ -74,6 +75,7 @@ void SystemTask::Work() {
spiNorFlash.Wakeup();
nimbleController.Init();
nimbleController.StartAdvertising();
+ brightnessController.Init();
lcd.Init();
twiMaster.Init();
@@ -81,14 +83,16 @@ void SystemTask::Work() {
batteryController.Init();
motorController.Init();
+ settingsController.Init();
+
displayApp.reset(new Pinetime::Applications::DisplayApp(lcd, lvgl, touchPanel, batteryController, bleController,
dateTimeController, watchdogView, *this, notificationManager,
- heartRateController));
+ heartRateController, settingsController));
displayApp->Start();
batteryController.Update();
- displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::UpdateBatteryLevel);
+ displayApp->PushMessage(Pinetime::Applications::Display::Messages::UpdateBatteryLevel);
heartRateSensor.Init();
heartRateSensor.Disable();
@@ -141,8 +145,8 @@ void SystemTask::Work() {
touchPanel.Wakeup();
lcd.Wakeup();
- displayApp->PushMessage(Applications::DisplayApp::Messages::GoToRunning);
- displayApp->PushMessage(Applications::DisplayApp::Messages::UpdateBatteryLevel);
+ displayApp->PushMessage(Pinetime::Applications::Display::Messages::GoToRunning);
+ displayApp->PushMessage(Pinetime::Applications::Display::Messages::UpdateBatteryLevel);
heartRateApp->PushMessage(Pinetime::Applications::HeartRateTask::Messages::WakeUp);
isSleeping = false;
@@ -152,17 +156,17 @@ void SystemTask::Work() {
isGoingToSleep = true;
NRF_LOG_INFO("[systemtask] Going to sleep");
xTimerStop(idleTimer, 0);
- displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::GoToSleep);
+ displayApp->PushMessage(Pinetime::Applications::Display::Messages::GoToSleep);
heartRateApp->PushMessage(Pinetime::Applications::HeartRateTask::Messages::GoToSleep);
break;
case Messages::OnNewTime:
ReloadIdleTimer();
- displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::UpdateDateTime);
+ displayApp->PushMessage(Pinetime::Applications::Display::Messages::UpdateDateTime);
break;
case Messages::OnNewNotification:
if(isSleeping && !isWakingUp) GoToRunning();
if(notificationManager.IsVibrationEnabled()) motorController.SetDuration(35);
- displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::NewNotification);
+ displayApp->PushMessage(Pinetime::Applications::Display::Messages::NewNotification);
break;
case Messages::BleConnected:
ReloadIdleTimer();
@@ -172,7 +176,7 @@ void SystemTask::Work() {
case Messages::BleFirmwareUpdateStarted:
doNotGoToSleep = true;
if(isSleeping && !isWakingUp) GoToRunning();
- displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::BleFirmwareUpdateStarted);
+ displayApp->PushMessage(Pinetime::Applications::Display::Messages::BleFirmwareUpdateStarted);
break;
case Messages::BleFirmwareUpdateFinished:
doNotGoToSleep = false;
@@ -230,7 +234,7 @@ void SystemTask::OnButtonPushed() {
if(!isSleeping) {
NRF_LOG_INFO("[systemtask] Button pushed");
PushMessage(Messages::OnButtonEvent);
- displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::ButtonPushed);
+ displayApp->PushMessage(Pinetime::Applications::Display::Messages::ButtonPushed);
}
else {
if(!isWakingUp) {
@@ -250,7 +254,7 @@ void SystemTask::OnTouchEvent() {
NRF_LOG_INFO("[systemtask] Touch event");
if(!isSleeping) {
PushMessage(Messages::OnTouchEvent);
- displayApp->PushMessage(Pinetime::Applications::DisplayApp::Messages::TouchEvent);
+ displayApp->PushMessage(Pinetime::Applications::Display::Messages::TouchEvent);
}
}
diff --git a/src/systemtask/SystemTask.h b/src/systemtask/SystemTask.h
index c650d085..cc693735 100644
--- a/src/systemtask/SystemTask.h
+++ b/src/systemtask/SystemTask.h
@@ -7,13 +7,22 @@
#include
#include
#include
+#include
#include "SystemMonitor.h"
#include "components/battery/BatteryController.h"
#include "components/ble/NimbleController.h"
#include "components/ble/NotificationManager.h"
#include "components/motor/MotorController.h"
+#ifdef PINETIME_IS_RECOVERY
+#include "displayapp/DisplayAppRecovery.h"
+#include "displayapp/DummyLittleVgl.h"
+#else
+#include "components/settings/Settings.h"
#include "displayapp/DisplayApp.h"
+#include "displayapp/LittleVgl.h"
+#endif
+
#include "drivers/Watchdog.h"
namespace Pinetime {
@@ -39,7 +48,8 @@ namespace Pinetime {
Controllers::Battery &batteryController, Controllers::Ble &bleController,
Controllers::DateTime &dateTimeController,
Pinetime::Controllers::MotorController& motorController,
- Pinetime::Drivers::Hrs3300& heartRateSensor);
+ Pinetime::Drivers::Hrs3300& heartRateSensor,
+ Controllers::Settings &settingsController);
void Start();
@@ -77,7 +87,9 @@ namespace Pinetime {
Pinetime::Controllers::NotificationManager notificationManager;
Pinetime::Controllers::MotorController& motorController;
Pinetime::Drivers::Hrs3300& heartRateSensor;
+ Pinetime::Controllers::Settings& settingsController;
Pinetime::Controllers::NimbleController nimbleController;
+ Controllers::BrightnessController brightnessController;
static constexpr uint8_t pinSpiSck = 2;
static constexpr uint8_t pinSpiMosi = 3;
diff --git a/tools/bin2c.py b/tools/bin2c.py
new file mode 100644
index 00000000..1d66656a
--- /dev/null
+++ b/tools/bin2c.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+#-*- coding: utf-8 -*-
+"""
+ bin2c
+ ~~~~~
+
+ Simple tool for creating C array from a binary file.
+
+ :copyright: (c) 2016 by Dmitry Alimov.
+ :license: The MIT License (MIT), see LICENSE for more details.
+"""
+
+import argparse
+import os
+import re
+import sys
+
+PY3 = sys.version_info[0] == 3
+
+
+def bin2c(filename, varname='data', linesize=80, indent=4):
+ """ Read binary data from file and return as a C array
+
+ :param filename: a filename of a file to read.
+ :param varname: a C array variable name.
+ :param linesize: a size of a line (min value is 40).
+ :param indent: an indent (number of spaces) that prepend each line.
+ """
+ if not os.path.isfile(filename):
+ print('File "%s" is not found!' % filename)
+ return ''
+ if not re.match('[a-zA-Z_][a-zA-Z0-9_]*', varname):
+ print('Invalid variable name "%s"' % varname)
+ return
+ with open(filename, 'rb') as in_file:
+ data = in_file.read()
+ # limit the line length
+ if linesize < 40:
+ linesize = 40
+ byte_len = 6 # '0x00, '
+ out = 'const char %s[%d] = {\n' % (varname, len(data))
+ line = ''
+ for byte in data:
+ line += '0x%02x, ' % (byte if PY3 else ord(byte))
+ if len(line) + indent + byte_len >= linesize:
+ out += ' ' * indent + line + '\n'
+ line = ''
+ # add the last line
+ if len(line) + indent + byte_len < linesize:
+ out += ' ' * indent + line + '\n'
+ # strip the last comma
+ out = out.rstrip(', \n') + '\n'
+ out += '};'
+ return out
+
+
+def main():
+ """ Main func """
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ 'filename', help='filename to convert to C array')
+ parser.add_argument(
+ 'varname', nargs='?', help='variable name', default='data')
+ parser.add_argument(
+ 'linesize', nargs='?', help='line length', default=80, type=int)
+ parser.add_argument(
+ 'indent', nargs='?', help='indent size', default=4, type=int)
+ args = parser.parse_args()
+ # print out the data
+ print(bin2c(args.filename, args.varname, args.linesize, args.indent))
+
+
+if __name__ == '__main__':
+ main()
\ No newline at end of file
diff --git a/tools/mcuboot/README b/tools/mcuboot/README
new file mode 100644
index 00000000..feb5d2f9
--- /dev/null
+++ b/tools/mcuboot/README
@@ -0,0 +1 @@
+This whole folder comes from MCUBoot source files (commit 9015a5d404c2c688166cab81067be53c860d98f4).
\ No newline at end of file
diff --git a/tools/mcuboot/assemble.py b/tools/mcuboot/assemble.py
new file mode 100644
index 00000000..f2ce4a1b
--- /dev/null
+++ b/tools/mcuboot/assemble.py
@@ -0,0 +1,131 @@
+#! /usr/bin/env python3
+#
+# Copyright 2017 Linaro Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Assemble multiple images into a single image that can be flashed on the device.
+"""
+
+import argparse
+import errno
+import io
+import re
+import os.path
+import sys
+
+ZEPHYR_BASE = os.getenv("ZEPHYR_BASE")
+if not ZEPHYR_BASE:
+ sys.exit("$ZEPHYR_BASE environment variable undefined")
+
+sys.path.insert(0, os.path.join(ZEPHYR_BASE, "scripts", "dts"))
+import edtlib
+
+def same_keys(a, b):
+ """Determine if the dicts a and b have the same keys in them"""
+ for ak in a.keys():
+ if ak not in b:
+ return False
+ for bk in b.keys():
+ if bk not in a:
+ return False
+ return True
+
+offset_re = re.compile(r"^#define DT_FLASH_AREA_([0-9A-Z_]+)_OFFSET(_0)?\s+(0x[0-9a-fA-F]+|[0-9]+)$")
+size_re = re.compile(r"^#define DT_FLASH_AREA_([0-9A-Z_]+)_SIZE(_0)?\s+(0x[0-9a-fA-F]+|[0-9]+)$")
+
+class Assembly():
+ def __init__(self, output, bootdir, edt):
+ self.find_slots(edt)
+ try:
+ os.unlink(output)
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ raise
+ self.output = output
+
+ def find_slots(self, edt):
+ offsets = {}
+ sizes = {}
+
+ part_nodes = edt.compat2nodes["fixed-partitions"]
+ for node in part_nodes:
+ for child in node.children.values():
+ if "label" in child.props:
+ label = child.props["label"].val
+ offsets[label] = child.regs[0].addr
+ sizes[label] = child.regs[0].size
+
+ if not same_keys(offsets, sizes):
+ raise Exception("Inconsistent data in devicetree.h")
+
+ # We care about the mcuboot, image-0, and image-1 partitions.
+ if 'mcuboot' not in offsets:
+ raise Exception("Board partition table does not have mcuboot partition")
+
+ if 'image-0' not in offsets:
+ raise Exception("Board partition table does not have image-0 partition")
+
+ if 'image-1' not in offsets:
+ raise Exception("Board partition table does not have image-1 partition")
+
+ self.offsets = offsets
+ self.sizes = sizes
+
+ def add_image(self, source, partition):
+ with open(self.output, 'ab') as ofd:
+ pos = ofd.tell()
+ print("partition {}, pos={}, offset={}".format(partition, pos, self.offsets[partition]))
+ if pos > self.offsets[partition]:
+ raise Exception("Partitions not in order, unsupported")
+ if pos < self.offsets[partition]:
+ buf = b'\xFF' * (self.offsets[partition] - pos)
+ ofd.write(buf)
+ with open(source, 'rb') as rfd:
+ ibuf = rfd.read()
+ if len(ibuf) > self.sizes[partition]:
+ raise Exception("Image {} is too large for partition".format(source))
+ ofd.write(ibuf)
+
+def main():
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument('-b', '--bootdir', required=True,
+ help='Directory of built bootloader')
+ parser.add_argument('-p', '--primary', required=True,
+ help='Signed image file for primary image')
+ parser.add_argument('-s', '--secondary',
+ help='Signed image file for secondary image')
+ parser.add_argument('-o', '--output', required=True,
+ help='Filename to write full image to')
+
+ args = parser.parse_args()
+
+ # Extract board name from path
+ board = os.path.split(os.path.split(args.bootdir)[0])[1]
+
+ dts_path = os.path.join(args.bootdir, "zephyr", board + ".dts.pre.tmp")
+
+ edt = edtlib.EDT(dts_path, [os.path.join(ZEPHYR_BASE, "dts", "bindings")],
+ warn_reg_unit_address_mismatch=False)
+
+ output = Assembly(args.output, args.bootdir, edt)
+
+ output.add_image(os.path.join(args.bootdir, 'zephyr', 'zephyr.bin'), 'mcuboot')
+ output.add_image(args.primary, "image-0")
+ if args.secondary is not None:
+ output.add_image(args.secondary, "image-1")
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/mcuboot/flash.sh b/tools/mcuboot/flash.sh
new file mode 100644
index 00000000..a2c58c75
--- /dev/null
+++ b/tools/mcuboot/flash.sh
@@ -0,0 +1,18 @@
+#! /bin/bash
+
+source $(dirname $0)/../target.sh
+
+lscript=/tmp/flash$$.jlink
+
+cat >$lscript < $gscript < {};
+let
+ # Nixpkgs has fairly recent versions of the dependencies, so we can
+ # rely on them without having to build our own derivations.
+ imgtoolPythonEnv = python37.withPackages (
+ _: [
+ python37.pkgs.click
+ python37.pkgs.cryptography
+ python37.pkgs.intelhex
+ python37.pkgs.setuptools
+ python37.pkgs.cbor
+ ]
+ );
+in
+myEnvFun {
+ name = "imgtool";
+
+ buildInputs = [ imgtoolPythonEnv ];
+}
diff --git a/tools/mcuboot/imgtool.py b/tools/mcuboot/imgtool.py
new file mode 100755
index 00000000..78614745
--- /dev/null
+++ b/tools/mcuboot/imgtool.py
@@ -0,0 +1,20 @@
+#! /usr/bin/env python3
+#
+# Copyright 2017 Linaro Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from imgtool import main
+
+if __name__ == '__main__':
+ main.imgtool()
diff --git a/tools/mcuboot/imgtool/__init__.py b/tools/mcuboot/imgtool/__init__.py
new file mode 100644
index 00000000..c0c3ef21
--- /dev/null
+++ b/tools/mcuboot/imgtool/__init__.py
@@ -0,0 +1,15 @@
+# Copyright 2017 Linaro Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+imgtool_version = "1.6.0rc2"
diff --git a/tools/mcuboot/imgtool/boot_record.py b/tools/mcuboot/imgtool/boot_record.py
new file mode 100644
index 00000000..4112b225
--- /dev/null
+++ b/tools/mcuboot/imgtool/boot_record.py
@@ -0,0 +1,47 @@
+# Copyright (c) 2019, Arm Limited.
+# Copyright (c) 2020, Linaro Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from enum import Enum
+import cbor
+
+
+class SwComponent(int, Enum):
+ """
+ Software component property IDs specified by
+ Arm's PSA Attestation API 1.0 document.
+ """
+ TYPE = 1
+ MEASUREMENT_VALUE = 2
+ VERSION = 4
+ SIGNER_ID = 5
+ MEASUREMENT_DESCRIPTION = 6
+
+
+def create_sw_component_data(sw_type, sw_version, sw_measurement_description,
+ sw_measurement_value, sw_signer_id):
+
+ # List of software component properties (Key ID + value)
+ properties = {
+ SwComponent.TYPE: sw_type,
+ SwComponent.VERSION: sw_version,
+ SwComponent.SIGNER_ID: sw_signer_id,
+ SwComponent.MEASUREMENT_DESCRIPTION: sw_measurement_description,
+ }
+
+ # Note: The measurement value must be the last item of the property
+ # list because later it will be modified by the bootloader.
+ properties[SwComponent.MEASUREMENT_VALUE] = sw_measurement_value
+
+ return cbor.dumps(properties)
diff --git a/tools/mcuboot/imgtool/image.py b/tools/mcuboot/imgtool/image.py
new file mode 100644
index 00000000..291134d7
--- /dev/null
+++ b/tools/mcuboot/imgtool/image.py
@@ -0,0 +1,552 @@
+# Copyright 2018 Nordic Semiconductor ASA
+# Copyright 2017 Linaro Limited
+# Copyright 2019-2020 Arm Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Image signing and management.
+"""
+
+from . import version as versmod
+from .boot_record import create_sw_component_data
+import click
+from enum import Enum
+from intelhex import IntelHex
+import hashlib
+import struct
+import os.path
+from .keys import rsa, ecdsa, x25519
+from cryptography.hazmat.primitives.asymmetric import ec, padding
+from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
+from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
+from cryptography.hazmat.primitives.kdf.hkdf import HKDF
+from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import hashes, hmac
+from cryptography.exceptions import InvalidSignature
+
+IMAGE_MAGIC = 0x96f3b83d
+IMAGE_HEADER_SIZE = 32
+BIN_EXT = "bin"
+INTEL_HEX_EXT = "hex"
+DEFAULT_MAX_SECTORS = 128
+MAX_ALIGN = 8
+DEP_IMAGES_KEY = "images"
+DEP_VERSIONS_KEY = "versions"
+MAX_SW_TYPE_LENGTH = 12 # Bytes
+
+# Image header flags.
+IMAGE_F = {
+ 'PIC': 0x0000001,
+ 'NON_BOOTABLE': 0x0000010,
+ 'RAM_LOAD': 0x0000020,
+ 'ENCRYPTED': 0x0000004,
+}
+
+TLV_VALUES = {
+ 'KEYHASH': 0x01,
+ 'PUBKEY': 0x02,
+ 'SHA256': 0x10,
+ 'RSA2048': 0x20,
+ 'ECDSA224': 0x21,
+ 'ECDSA256': 0x22,
+ 'RSA3072': 0x23,
+ 'ED25519': 0x24,
+ 'ENCRSA2048': 0x30,
+ 'ENCKW128': 0x31,
+ 'ENCEC256': 0x32,
+ 'ENCX25519': 0x33,
+ 'DEPENDENCY': 0x40,
+ 'SEC_CNT': 0x50,
+ 'BOOT_RECORD': 0x60,
+}
+
+TLV_SIZE = 4
+TLV_INFO_SIZE = 4
+TLV_INFO_MAGIC = 0x6907
+TLV_PROT_INFO_MAGIC = 0x6908
+
+boot_magic = bytes([
+ 0x77, 0xc2, 0x95, 0xf3,
+ 0x60, 0xd2, 0xef, 0x7f,
+ 0x35, 0x52, 0x50, 0x0f,
+ 0x2c, 0xb6, 0x79, 0x80, ])
+
+STRUCT_ENDIAN_DICT = {
+ 'little': '<',
+ 'big': '>'
+}
+
+VerifyResult = Enum('VerifyResult',
+ """
+ OK INVALID_MAGIC INVALID_TLV_INFO_MAGIC INVALID_HASH
+ INVALID_SIGNATURE
+ """)
+
+
+class TLV():
+ def __init__(self, endian, magic=TLV_INFO_MAGIC):
+ self.magic = magic
+ self.buf = bytearray()
+ self.endian = endian
+
+ def __len__(self):
+ return TLV_INFO_SIZE + len(self.buf)
+
+ def add(self, kind, payload):
+ """
+ Add a TLV record. Kind should be a string found in TLV_VALUES above.
+ """
+ e = STRUCT_ENDIAN_DICT[self.endian]
+ buf = struct.pack(e + 'BBH', TLV_VALUES[kind], 0, len(payload))
+ self.buf += buf
+ self.buf += payload
+
+ def get(self):
+ if len(self.buf) == 0:
+ return bytes()
+ e = STRUCT_ENDIAN_DICT[self.endian]
+ header = struct.pack(e + 'HH', self.magic, len(self))
+ return header + bytes(self.buf)
+
+
+class Image():
+
+ def __init__(self, version=None, header_size=IMAGE_HEADER_SIZE,
+ pad_header=False, pad=False, confirm=False, align=1,
+ slot_size=0, max_sectors=DEFAULT_MAX_SECTORS,
+ overwrite_only=False, endian="little", load_addr=0,
+ erased_val=None, save_enctlv=False, security_counter=None):
+ self.version = version or versmod.decode_version("0")
+ self.header_size = header_size
+ self.pad_header = pad_header
+ self.pad = pad
+ self.confirm = confirm
+ self.align = align
+ self.slot_size = slot_size
+ self.max_sectors = max_sectors
+ self.overwrite_only = overwrite_only
+ self.endian = endian
+ self.base_addr = None
+ self.load_addr = 0 if load_addr is None else load_addr
+ self.erased_val = 0xff if erased_val is None else int(erased_val, 0)
+ self.payload = []
+ self.enckey = None
+ self.save_enctlv = save_enctlv
+ self.enctlv_len = 0
+
+ if security_counter == 'auto':
+ # Security counter has not been explicitly provided,
+ # generate it from the version number
+ self.security_counter = ((self.version.major << 24)
+ + (self.version.minor << 16)
+ + self.version.revision)
+ else:
+ self.security_counter = security_counter
+
+ def __repr__(self):
+ return "".format(
+ self.version,
+ self.header_size,
+ self.security_counter,
+ self.base_addr if self.base_addr is not None else "N/A",
+ self.load_addr,
+ self.align,
+ self.slot_size,
+ self.max_sectors,
+ self.overwrite_only,
+ self.endian,
+ self.__class__.__name__,
+ len(self.payload))
+
+ def load(self, path):
+ """Load an image from a given file"""
+ ext = os.path.splitext(path)[1][1:].lower()
+ try:
+ if ext == INTEL_HEX_EXT:
+ ih = IntelHex(path)
+ self.payload = ih.tobinarray()
+ self.base_addr = ih.minaddr()
+ else:
+ with open(path, 'rb') as f:
+ self.payload = f.read()
+ except FileNotFoundError:
+ raise click.UsageError("Input file not found")
+
+ # Add the image header if needed.
+ if self.pad_header and self.header_size > 0:
+ if self.base_addr:
+ # Adjust base_addr for new header
+ self.base_addr -= self.header_size
+ self.payload = bytes([self.erased_val] * self.header_size) + \
+ self.payload
+
+ self.check_header()
+
+ def save(self, path, hex_addr=None):
+ """Save an image from a given file"""
+ ext = os.path.splitext(path)[1][1:].lower()
+ if ext == INTEL_HEX_EXT:
+ # input was in binary format, but HEX needs to know the base addr
+ if self.base_addr is None and hex_addr is None:
+ raise click.UsageError("No address exists in input file "
+ "neither was it provided by user")
+ h = IntelHex()
+ if hex_addr is not None:
+ self.base_addr = hex_addr
+ h.frombytes(bytes=self.payload, offset=self.base_addr)
+ if self.pad:
+ trailer_size = self._trailer_size(self.align, self.max_sectors,
+ self.overwrite_only,
+ self.enckey,
+ self.save_enctlv,
+ self.enctlv_len)
+ trailer_addr = (self.base_addr + self.slot_size) - trailer_size
+ padding = bytes([self.erased_val] *
+ (trailer_size - len(boot_magic))) + boot_magic
+ h.puts(trailer_addr, padding)
+ h.tofile(path, 'hex')
+ else:
+ if self.pad:
+ self.pad_to(self.slot_size)
+ with open(path, 'wb') as f:
+ f.write(self.payload)
+
+ def check_header(self):
+ if self.header_size > 0 and not self.pad_header:
+ if any(v != 0 for v in self.payload[0:self.header_size]):
+ raise click.UsageError("Header padding was not requested and "
+ "image does not start with zeros")
+
+ def check_trailer(self):
+ if self.slot_size > 0:
+ tsize = self._trailer_size(self.align, self.max_sectors,
+ self.overwrite_only, self.enckey,
+ self.save_enctlv, self.enctlv_len)
+ padding = self.slot_size - (len(self.payload) + tsize)
+ if padding < 0:
+ msg = "Image size (0x{:x}) + trailer (0x{:x}) exceeds " \
+ "requested size 0x{:x}".format(
+ len(self.payload), tsize, self.slot_size)
+ raise click.UsageError(msg)
+
+ def ecies_hkdf(self, enckey, plainkey):
+ if isinstance(enckey, ecdsa.ECDSA256P1Public):
+ newpk = ec.generate_private_key(ec.SECP256R1(), default_backend())
+ shared = newpk.exchange(ec.ECDH(), enckey._get_public())
+ else:
+ newpk = X25519PrivateKey.generate()
+ shared = newpk.exchange(enckey._get_public())
+ derived_key = HKDF(
+ algorithm=hashes.SHA256(), length=48, salt=None,
+ info=b'MCUBoot_ECIES_v1', backend=default_backend()).derive(shared)
+ encryptor = Cipher(algorithms.AES(derived_key[:16]),
+ modes.CTR(bytes([0] * 16)),
+ backend=default_backend()).encryptor()
+ cipherkey = encryptor.update(plainkey) + encryptor.finalize()
+ mac = hmac.HMAC(derived_key[16:], hashes.SHA256(),
+ backend=default_backend())
+ mac.update(cipherkey)
+ ciphermac = mac.finalize()
+ if isinstance(enckey, ecdsa.ECDSA256P1Public):
+ pubk = newpk.public_key().public_bytes(
+ encoding=Encoding.X962,
+ format=PublicFormat.UncompressedPoint)
+ else:
+ pubk = newpk.public_key().public_bytes(
+ encoding=Encoding.Raw,
+ format=PublicFormat.Raw)
+ return cipherkey, ciphermac, pubk
+
+ def create(self, key, public_key_format, enckey, dependencies=None,
+ sw_type=None):
+ self.enckey = enckey
+
+ # Calculate the hash of the public key
+ if key is not None:
+ pub = key.get_public_bytes()
+ sha = hashlib.sha256()
+ sha.update(pub)
+ pubbytes = sha.digest()
+ else:
+ pubbytes = bytes(hashlib.sha256().digest_size)
+
+ protected_tlv_size = 0
+
+ if self.security_counter is not None:
+ # Size of the security counter TLV: header ('HH') + payload ('I')
+ # = 4 + 4 = 8 Bytes
+ protected_tlv_size += TLV_SIZE + 4
+
+ if sw_type is not None:
+ if len(sw_type) > MAX_SW_TYPE_LENGTH:
+ msg = "'{}' is too long ({} characters) for sw_type. Its " \
+ "maximum allowed length is 12 characters.".format(
+ sw_type, len(sw_type))
+ raise click.UsageError(msg)
+
+ image_version = (str(self.version.major) + '.'
+ + str(self.version.minor) + '.'
+ + str(self.version.revision))
+
+ # The image hash is computed over the image header, the image
+ # itself and the protected TLV area. However, the boot record TLV
+ # (which is part of the protected area) should contain this hash
+ # before it is even calculated. For this reason the script fills
+ # this field with zeros and the bootloader will insert the right
+ # value later.
+ digest = bytes(hashlib.sha256().digest_size)
+
+ # Create CBOR encoded boot record
+ boot_record = create_sw_component_data(sw_type, image_version,
+ "SHA256", digest,
+ pubbytes)
+
+ protected_tlv_size += TLV_SIZE + len(boot_record)
+
+ if dependencies is not None:
+ # Size of a Dependency TLV = Header ('HH') + Payload('IBBHI')
+ # = 4 + 12 = 16 Bytes
+ dependencies_num = len(dependencies[DEP_IMAGES_KEY])
+ protected_tlv_size += (dependencies_num * 16)
+
+ if protected_tlv_size != 0:
+ # Add the size of the TLV info header
+ protected_tlv_size += TLV_INFO_SIZE
+
+ # At this point the image is already on the payload, this adds
+ # the header to the payload as well
+ self.add_header(enckey, protected_tlv_size)
+
+ prot_tlv = TLV(self.endian, TLV_PROT_INFO_MAGIC)
+
+ # Protected TLVs must be added first, because they are also included
+ # in the hash calculation
+ protected_tlv_off = None
+ if protected_tlv_size != 0:
+
+ e = STRUCT_ENDIAN_DICT[self.endian]
+
+ if self.security_counter is not None:
+ payload = struct.pack(e + 'I', self.security_counter)
+ prot_tlv.add('SEC_CNT', payload)
+
+ if sw_type is not None:
+ prot_tlv.add('BOOT_RECORD', boot_record)
+
+ if dependencies is not None:
+ for i in range(dependencies_num):
+ payload = struct.pack(
+ e + 'B3x'+'BBHI',
+ int(dependencies[DEP_IMAGES_KEY][i]),
+ dependencies[DEP_VERSIONS_KEY][i].major,
+ dependencies[DEP_VERSIONS_KEY][i].minor,
+ dependencies[DEP_VERSIONS_KEY][i].revision,
+ dependencies[DEP_VERSIONS_KEY][i].build
+ )
+ prot_tlv.add('DEPENDENCY', payload)
+
+ protected_tlv_off = len(self.payload)
+ self.payload += prot_tlv.get()
+
+ tlv = TLV(self.endian)
+
+ # Note that ecdsa wants to do the hashing itself, which means
+ # we get to hash it twice.
+ sha = hashlib.sha256()
+ sha.update(self.payload)
+ digest = sha.digest()
+
+ tlv.add('SHA256', digest)
+
+ if key is not None:
+ if public_key_format == 'hash':
+ tlv.add('KEYHASH', pubbytes)
+ else:
+ tlv.add('PUBKEY', pub)
+
+ # `sign` expects the full image payload (sha256 done internally),
+ # while `sign_digest` expects only the digest of the payload
+
+ if hasattr(key, 'sign'):
+ sig = key.sign(bytes(self.payload))
+ else:
+ sig = key.sign_digest(digest)
+ tlv.add(key.sig_tlv(), sig)
+
+ # At this point the image was hashed + signed, we can remove the
+ # protected TLVs from the payload (will be re-added later)
+ if protected_tlv_off is not None:
+ self.payload = self.payload[:protected_tlv_off]
+
+ if enckey is not None:
+ plainkey = os.urandom(16)
+
+ if isinstance(enckey, rsa.RSAPublic):
+ cipherkey = enckey._get_public().encrypt(
+ plainkey, padding.OAEP(
+ mgf=padding.MGF1(algorithm=hashes.SHA256()),
+ algorithm=hashes.SHA256(),
+ label=None))
+ self.enctlv_len = len(cipherkey)
+ tlv.add('ENCRSA2048', cipherkey)
+ elif isinstance(enckey, (ecdsa.ECDSA256P1Public,
+ x25519.X25519Public)):
+ cipherkey, mac, pubk = self.ecies_hkdf(enckey, plainkey)
+ enctlv = pubk + mac + cipherkey
+ self.enctlv_len = len(enctlv)
+ if isinstance(enckey, ecdsa.ECDSA256P1Public):
+ tlv.add('ENCEC256', enctlv)
+ else:
+ tlv.add('ENCX25519', enctlv)
+
+ nonce = bytes([0] * 16)
+ cipher = Cipher(algorithms.AES(plainkey), modes.CTR(nonce),
+ backend=default_backend())
+ encryptor = cipher.encryptor()
+ img = bytes(self.payload[self.header_size:])
+ self.payload[self.header_size:] = \
+ encryptor.update(img) + encryptor.finalize()
+
+ self.payload += prot_tlv.get()
+ self.payload += tlv.get()
+
+ self.check_trailer()
+
+ def add_header(self, enckey, protected_tlv_size):
+ """Install the image header."""
+
+ flags = 0
+ if enckey is not None:
+ flags |= IMAGE_F['ENCRYPTED']
+ if self.load_addr != 0:
+ # Indicates that this image should be loaded into RAM
+ # instead of run directly from flash.
+ flags |= IMAGE_F['RAM_LOAD']
+
+ e = STRUCT_ENDIAN_DICT[self.endian]
+ fmt = (e +
+ # type ImageHdr struct {
+ 'I' + # Magic uint32
+ 'I' + # LoadAddr uint32
+ 'H' + # HdrSz uint16
+ 'H' + # PTLVSz uint16
+ 'I' + # ImgSz uint32
+ 'I' + # Flags uint32
+ 'BBHI' + # Vers ImageVersion
+ 'I' # Pad1 uint32
+ ) # }
+ assert struct.calcsize(fmt) == IMAGE_HEADER_SIZE
+ header = struct.pack(fmt,
+ IMAGE_MAGIC,
+ self.load_addr,
+ self.header_size,
+ protected_tlv_size, # TLV Info header + Protected TLVs
+ len(self.payload) - self.header_size, # ImageSz
+ flags,
+ self.version.major,
+ self.version.minor or 0,
+ self.version.revision or 0,
+ self.version.build or 0,
+ 0) # Pad1
+ self.payload = bytearray(self.payload)
+ self.payload[:len(header)] = header
+
+ def _trailer_size(self, write_size, max_sectors, overwrite_only, enckey,
+ save_enctlv, enctlv_len):
+ # NOTE: should already be checked by the argument parser
+ magic_size = 16
+ if overwrite_only:
+ return MAX_ALIGN * 2 + magic_size
+ else:
+ if write_size not in set([1, 2, 4, 8]):
+ raise click.BadParameter("Invalid alignment: {}".format(
+ write_size))
+ m = DEFAULT_MAX_SECTORS if max_sectors is None else max_sectors
+ trailer = m * 3 * write_size # status area
+ if enckey is not None:
+ if save_enctlv:
+ # TLV saved by the bootloader is aligned
+ keylen = (int((enctlv_len - 1) / MAX_ALIGN) + 1) * MAX_ALIGN
+ else:
+ keylen = 16
+ trailer += keylen * 2 # encryption keys
+ trailer += MAX_ALIGN * 4 # image_ok/copy_done/swap_info/swap_size
+ trailer += magic_size
+ return trailer
+
+ def pad_to(self, size):
+ """Pad the image to the given size, with the given flash alignment."""
+ tsize = self._trailer_size(self.align, self.max_sectors,
+ self.overwrite_only, self.enckey,
+ self.save_enctlv, self.enctlv_len)
+ padding = size - (len(self.payload) + tsize)
+ pbytes = bytearray([self.erased_val] * padding)
+ pbytes += bytearray([self.erased_val] * (tsize - len(boot_magic)))
+ if self.confirm and not self.overwrite_only:
+ pbytes[-MAX_ALIGN] = 0x01 # image_ok = 0x01
+ pbytes += boot_magic
+ self.payload += pbytes
+
+ @staticmethod
+ def verify(imgfile, key):
+ with open(imgfile, "rb") as f:
+ b = f.read()
+
+ magic, _, header_size, _, img_size = struct.unpack('IIHHI', b[:16])
+ version = struct.unpack('BBHI', b[20:28])
+
+ if magic != IMAGE_MAGIC:
+ return VerifyResult.INVALID_MAGIC, None
+
+ tlv_info = b[header_size+img_size:header_size+img_size+TLV_INFO_SIZE]
+ magic, tlv_tot = struct.unpack('HH', tlv_info)
+ if magic != TLV_INFO_MAGIC:
+ return VerifyResult.INVALID_TLV_INFO_MAGIC, None
+
+ sha = hashlib.sha256()
+ sha.update(b[:header_size+img_size])
+ digest = sha.digest()
+
+ tlv_off = header_size + img_size
+ tlv_end = tlv_off + tlv_tot
+ tlv_off += TLV_INFO_SIZE # skip tlv info
+ while tlv_off < tlv_end:
+ tlv = b[tlv_off:tlv_off+TLV_SIZE]
+ tlv_type, _, tlv_len = struct.unpack('BBH', tlv)
+ if tlv_type == TLV_VALUES["SHA256"]:
+ off = tlv_off + TLV_SIZE
+ if digest == b[off:off+tlv_len]:
+ if key is None:
+ return VerifyResult.OK, version
+ else:
+ return VerifyResult.INVALID_HASH, None
+ elif key is not None and tlv_type == TLV_VALUES[key.sig_tlv()]:
+ off = tlv_off + TLV_SIZE
+ tlv_sig = b[off:off+tlv_len]
+ payload = b[:header_size+img_size]
+ try:
+ if hasattr(key, 'verify'):
+ key.verify(tlv_sig, payload)
+ else:
+ key.verify_digest(tlv_sig, digest)
+ return VerifyResult.OK, version
+ except InvalidSignature:
+ # continue to next TLV
+ pass
+ tlv_off += TLV_SIZE + tlv_len
+ return VerifyResult.INVALID_SIGNATURE, None
diff --git a/tools/mcuboot/imgtool/keys/__init__.py b/tools/mcuboot/imgtool/keys/__init__.py
new file mode 100644
index 00000000..f25e2aae
--- /dev/null
+++ b/tools/mcuboot/imgtool/keys/__init__.py
@@ -0,0 +1,94 @@
+# Copyright 2017 Linaro Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Cryptographic key management for imgtool.
+"""
+
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric.rsa import (
+ RSAPrivateKey, RSAPublicKey)
+from cryptography.hazmat.primitives.asymmetric.ec import (
+ EllipticCurvePrivateKey, EllipticCurvePublicKey)
+from cryptography.hazmat.primitives.asymmetric.ed25519 import (
+ Ed25519PrivateKey, Ed25519PublicKey)
+from cryptography.hazmat.primitives.asymmetric.x25519 import (
+ X25519PrivateKey, X25519PublicKey)
+
+from .rsa import RSA, RSAPublic, RSAUsageError, RSA_KEY_SIZES
+from .ecdsa import ECDSA256P1, ECDSA256P1Public, ECDSAUsageError
+from .ed25519 import Ed25519, Ed25519Public, Ed25519UsageError
+from .x25519 import X25519, X25519Public, X25519UsageError
+
+
+class PasswordRequired(Exception):
+ """Raised to indicate that the key is password protected, but a
+ password was not specified."""
+ pass
+
+
+def load(path, passwd=None):
+ """Try loading a key from the given path. Returns None if the password wasn't specified."""
+ with open(path, 'rb') as f:
+ raw_pem = f.read()
+ try:
+ pk = serialization.load_pem_private_key(
+ raw_pem,
+ password=passwd,
+ backend=default_backend())
+ # Unfortunately, the crypto library raises unhelpful exceptions,
+ # so we have to look at the text.
+ except TypeError as e:
+ msg = str(e)
+ if "private key is encrypted" in msg:
+ return None
+ raise e
+ except ValueError:
+ # This seems to happen if the key is a public key, let's try
+ # loading it as a public key.
+ pk = serialization.load_pem_public_key(
+ raw_pem,
+ backend=default_backend())
+
+ if isinstance(pk, RSAPrivateKey):
+ if pk.key_size not in RSA_KEY_SIZES:
+ raise Exception("Unsupported RSA key size: " + pk.key_size)
+ return RSA(pk)
+ elif isinstance(pk, RSAPublicKey):
+ if pk.key_size not in RSA_KEY_SIZES:
+ raise Exception("Unsupported RSA key size: " + pk.key_size)
+ return RSAPublic(pk)
+ elif isinstance(pk, EllipticCurvePrivateKey):
+ if pk.curve.name != 'secp256r1':
+ raise Exception("Unsupported EC curve: " + pk.curve.name)
+ if pk.key_size != 256:
+ raise Exception("Unsupported EC size: " + pk.key_size)
+ return ECDSA256P1(pk)
+ elif isinstance(pk, EllipticCurvePublicKey):
+ if pk.curve.name != 'secp256r1':
+ raise Exception("Unsupported EC curve: " + pk.curve.name)
+ if pk.key_size != 256:
+ raise Exception("Unsupported EC size: " + pk.key_size)
+ return ECDSA256P1Public(pk)
+ elif isinstance(pk, Ed25519PrivateKey):
+ return Ed25519(pk)
+ elif isinstance(pk, Ed25519PublicKey):
+ return Ed25519Public(pk)
+ elif isinstance(pk, X25519PrivateKey):
+ return X25519(pk)
+ elif isinstance(pk, X25519PublicKey):
+ return X25519Public(pk)
+ else:
+ raise Exception("Unknown key type: " + str(type(pk)))
diff --git a/tools/mcuboot/imgtool/keys/ecdsa.py b/tools/mcuboot/imgtool/keys/ecdsa.py
new file mode 100644
index 00000000..139d583d
--- /dev/null
+++ b/tools/mcuboot/imgtool/keys/ecdsa.py
@@ -0,0 +1,157 @@
+"""
+ECDSA key management
+"""
+
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric import ec
+from cryptography.hazmat.primitives.hashes import SHA256
+
+from .general import KeyClass
+
+class ECDSAUsageError(Exception):
+ pass
+
+class ECDSA256P1Public(KeyClass):
+ def __init__(self, key):
+ self.key = key
+
+ def shortname(self):
+ return "ecdsa"
+
+ def _unsupported(self, name):
+ raise ECDSAUsageError("Operation {} requires private key".format(name))
+
+ def _get_public(self):
+ return self.key
+
+ def get_public_bytes(self):
+ # The key is embedded into MBUboot in "SubjectPublicKeyInfo" format
+ return self._get_public().public_bytes(
+ encoding=serialization.Encoding.DER,
+ format=serialization.PublicFormat.SubjectPublicKeyInfo)
+
+ def get_private_bytes(self, minimal):
+ self._unsupported('get_private_bytes')
+
+ def export_private(self, path, passwd=None):
+ self._unsupported('export_private')
+
+ def export_public(self, path):
+ """Write the public key to the given file."""
+ pem = self._get_public().public_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PublicFormat.SubjectPublicKeyInfo)
+ with open(path, 'wb') as f:
+ f.write(pem)
+
+ def sig_type(self):
+ return "ECDSA256_SHA256"
+
+ def sig_tlv(self):
+ return "ECDSA256"
+
+ def sig_len(self):
+ # Early versions of MCUboot (< v1.5.0) required ECDSA
+ # signatures to be padded to 72 bytes. Because the DER
+ # encoding is done with signed integers, the size of the
+ # signature will vary depending on whether the high bit is set
+ # in each value. This padding was done in a
+ # not-easily-reversible way (by just adding zeros).
+ #
+ # The signing code no longer requires this padding, and newer
+ # versions of MCUboot don't require it. But, continue to
+ # return the total length so that the padding can be done if
+ # requested.
+ return 72
+
+ def verify(self, signature, payload):
+ # strip possible paddings added during sign
+ signature = signature[:signature[1] + 2]
+ k = self.key
+ if isinstance(self.key, ec.EllipticCurvePrivateKey):
+ k = self.key.public_key()
+ return k.verify(signature=signature, data=payload,
+ signature_algorithm=ec.ECDSA(SHA256()))
+
+
+class ECDSA256P1(ECDSA256P1Public):
+ """
+ Wrapper around an ECDSA private key.
+ """
+
+ def __init__(self, key):
+ """key should be an instance of EllipticCurvePrivateKey"""
+ self.key = key
+ self.pad_sig = False
+
+ @staticmethod
+ def generate():
+ pk = ec.generate_private_key(
+ ec.SECP256R1(),
+ backend=default_backend())
+ return ECDSA256P1(pk)
+
+ def _get_public(self):
+ return self.key.public_key()
+
+ def _build_minimal_ecdsa_privkey(self, der):
+ '''
+ Builds a new DER that only includes the EC private key, removing the
+ public key that is added as an "optional" BITSTRING.
+ '''
+ offset_PUB = 68
+ EXCEPTION_TEXT = "Error parsing ecdsa key. Please submit an issue!"
+ if der[offset_PUB] != 0xa1:
+ raise ECDSAUsageError(EXCEPTION_TEXT)
+ len_PUB = der[offset_PUB + 1]
+ b = bytearray(der[:-offset_PUB])
+ offset_SEQ = 29
+ if b[offset_SEQ] != 0x30:
+ raise ECDSAUsageError(EXCEPTION_TEXT)
+ b[offset_SEQ + 1] -= len_PUB
+ offset_OCT_STR = 27
+ if b[offset_OCT_STR] != 0x04:
+ raise ECDSAUsageError(EXCEPTION_TEXT)
+ b[offset_OCT_STR + 1] -= len_PUB
+ if b[0] != 0x30 or b[1] != 0x81:
+ raise ECDSAUsageError(EXCEPTION_TEXT)
+ b[2] -= len_PUB
+ return b
+
+ def get_private_bytes(self, minimal):
+ priv = self.key.private_bytes(
+ encoding=serialization.Encoding.DER,
+ format=serialization.PrivateFormat.PKCS8,
+ encryption_algorithm=serialization.NoEncryption())
+ if minimal:
+ priv = self._build_minimal_ecdsa_privkey(priv)
+ return priv
+
+ def export_private(self, path, passwd=None):
+ """Write the private key to the given file, protecting it with the optional password."""
+ if passwd is None:
+ enc = serialization.NoEncryption()
+ else:
+ enc = serialization.BestAvailableEncryption(passwd)
+ pem = self.key.private_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PrivateFormat.PKCS8,
+ encryption_algorithm=enc)
+ with open(path, 'wb') as f:
+ f.write(pem)
+
+ def raw_sign(self, payload):
+ """Return the actual signature"""
+ return self.key.sign(
+ data=payload,
+ signature_algorithm=ec.ECDSA(SHA256()))
+
+ def sign(self, payload):
+ sig = self.raw_sign(payload)
+ if self.pad_sig:
+ # To make fixed length, pad with one or two zeros.
+ sig += b'\000' * (self.sig_len() - len(sig))
+ return sig
+ else:
+ return sig
diff --git a/tools/mcuboot/imgtool/keys/ecdsa_test.py b/tools/mcuboot/imgtool/keys/ecdsa_test.py
new file mode 100644
index 00000000..7982cad9
--- /dev/null
+++ b/tools/mcuboot/imgtool/keys/ecdsa_test.py
@@ -0,0 +1,99 @@
+"""
+Tests for ECDSA keys
+"""
+
+import io
+import os.path
+import sys
+import tempfile
+import unittest
+
+from cryptography.exceptions import InvalidSignature
+from cryptography.hazmat.primitives.asymmetric import ec
+from cryptography.hazmat.primitives.hashes import SHA256
+
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
+
+from imgtool.keys import load, ECDSA256P1, ECDSAUsageError
+
+class EcKeyGeneration(unittest.TestCase):
+
+ def setUp(self):
+ self.test_dir = tempfile.TemporaryDirectory()
+
+ def tname(self, base):
+ return os.path.join(self.test_dir.name, base)
+
+ def tearDown(self):
+ self.test_dir.cleanup()
+
+ def test_keygen(self):
+ name1 = self.tname("keygen.pem")
+ k = ECDSA256P1.generate()
+ k.export_private(name1, b'secret')
+
+ self.assertIsNone(load(name1))
+
+ k2 = load(name1, b'secret')
+
+ pubname = self.tname('keygen-pub.pem')
+ k2.export_public(pubname)
+ pk2 = load(pubname)
+
+ # We should be able to export the public key from the loaded
+ # public key, but not the private key.
+ pk2.export_public(self.tname('keygen-pub2.pem'))
+ self.assertRaises(ECDSAUsageError,
+ pk2.export_private, self.tname('keygen-priv2.pem'))
+
+ def test_emit(self):
+ """Basic sanity check on the code emitters."""
+ k = ECDSA256P1.generate()
+
+ ccode = io.StringIO()
+ k.emit_c_public(ccode)
+ self.assertIn("ecdsa_pub_key", ccode.getvalue())
+ self.assertIn("ecdsa_pub_key_len", ccode.getvalue())
+
+ rustcode = io.StringIO()
+ k.emit_rust_public(rustcode)
+ self.assertIn("ECDSA_PUB_KEY", rustcode.getvalue())
+
+ def test_emit_pub(self):
+ """Basic sanity check on the code emitters."""
+ pubname = self.tname("public.pem")
+ k = ECDSA256P1.generate()
+ k.export_public(pubname)
+
+ k2 = load(pubname)
+
+ ccode = io.StringIO()
+ k2.emit_c_public(ccode)
+ self.assertIn("ecdsa_pub_key", ccode.getvalue())
+ self.assertIn("ecdsa_pub_key_len", ccode.getvalue())
+
+ rustcode = io.StringIO()
+ k2.emit_rust_public(rustcode)
+ self.assertIn("ECDSA_PUB_KEY", rustcode.getvalue())
+
+ def test_sig(self):
+ k = ECDSA256P1.generate()
+ buf = b'This is the message'
+ sig = k.raw_sign(buf)
+
+ # The code doesn't have any verification, so verify this
+ # manually.
+ k.key.public_key().verify(
+ signature=sig,
+ data=buf,
+ signature_algorithm=ec.ECDSA(SHA256()))
+
+ # Modify the message to make sure the signature fails.
+ self.assertRaises(InvalidSignature,
+ k.key.public_key().verify,
+ signature=sig,
+ data=b'This is thE message',
+ signature_algorithm=ec.ECDSA(SHA256()))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tools/mcuboot/imgtool/keys/ed25519.py b/tools/mcuboot/imgtool/keys/ed25519.py
new file mode 100644
index 00000000..fb000cd9
--- /dev/null
+++ b/tools/mcuboot/imgtool/keys/ed25519.py
@@ -0,0 +1,105 @@
+"""
+ED25519 key management
+"""
+
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric import ed25519
+
+from .general import KeyClass
+
+
+class Ed25519UsageError(Exception):
+ pass
+
+
+class Ed25519Public(KeyClass):
+ def __init__(self, key):
+ self.key = key
+
+ def shortname(self):
+ return "ed25519"
+
+ def _unsupported(self, name):
+ raise Ed25519UsageError("Operation {} requires private key".format(name))
+
+ def _get_public(self):
+ return self.key
+
+ def get_public_bytes(self):
+ # The key is embedded into MBUboot in "SubjectPublicKeyInfo" format
+ return self._get_public().public_bytes(
+ encoding=serialization.Encoding.DER,
+ format=serialization.PublicFormat.SubjectPublicKeyInfo)
+
+ def get_private_bytes(self, minimal):
+ self._unsupported('get_private_bytes')
+
+ def export_private(self, path, passwd=None):
+ self._unsupported('export_private')
+
+ def export_public(self, path):
+ """Write the public key to the given file."""
+ pem = self._get_public().public_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PublicFormat.SubjectPublicKeyInfo)
+ with open(path, 'wb') as f:
+ f.write(pem)
+
+ def sig_type(self):
+ return "ED25519"
+
+ def sig_tlv(self):
+ return "ED25519"
+
+ def sig_len(self):
+ return 64
+
+
+class Ed25519(Ed25519Public):
+ """
+ Wrapper around an ED25519 private key.
+ """
+
+ def __init__(self, key):
+ """key should be an instance of EllipticCurvePrivateKey"""
+ self.key = key
+
+ @staticmethod
+ def generate():
+ pk = ed25519.Ed25519PrivateKey.generate()
+ return Ed25519(pk)
+
+ def _get_public(self):
+ return self.key.public_key()
+
+ def get_private_bytes(self, minimal):
+ raise Ed25519UsageError("Operation not supported with {} keys".format(
+ self.shortname()))
+
+ def export_private(self, path, passwd=None):
+ """
+ Write the private key to the given file, protecting it with the
+ optional password.
+ """
+ if passwd is None:
+ enc = serialization.NoEncryption()
+ else:
+ enc = serialization.BestAvailableEncryption(passwd)
+ pem = self.key.private_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PrivateFormat.PKCS8,
+ encryption_algorithm=enc)
+ with open(path, 'wb') as f:
+ f.write(pem)
+
+ def sign_digest(self, digest):
+ """Return the actual signature"""
+ return self.key.sign(data=digest)
+
+ def verify_digest(self, signature, digest):
+ """Verify that signature is valid for given digest"""
+ k = self.key
+ if isinstance(self.key, ed25519.Ed25519PrivateKey):
+ k = self.key.public_key()
+ return k.verify(signature=signature, data=digest)
diff --git a/tools/mcuboot/imgtool/keys/ed25519_test.py b/tools/mcuboot/imgtool/keys/ed25519_test.py
new file mode 100644
index 00000000..31f43fe9
--- /dev/null
+++ b/tools/mcuboot/imgtool/keys/ed25519_test.py
@@ -0,0 +1,103 @@
+"""
+Tests for ECDSA keys
+"""
+
+import hashlib
+import io
+import os.path
+import sys
+import tempfile
+import unittest
+
+from cryptography.exceptions import InvalidSignature
+from cryptography.hazmat.primitives.asymmetric import ed25519
+
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..')))
+
+from imgtool.keys import load, Ed25519, Ed25519UsageError
+
+
+class Ed25519KeyGeneration(unittest.TestCase):
+
+ def setUp(self):
+ self.test_dir = tempfile.TemporaryDirectory()
+
+ def tname(self, base):
+ return os.path.join(self.test_dir.name, base)
+
+ def tearDown(self):
+ self.test_dir.cleanup()
+
+ def test_keygen(self):
+ name1 = self.tname("keygen.pem")
+ k = Ed25519.generate()
+ k.export_private(name1, b'secret')
+
+ self.assertIsNone(load(name1))
+
+ k2 = load(name1, b'secret')
+
+ pubname = self.tname('keygen-pub.pem')
+ k2.export_public(pubname)
+ pk2 = load(pubname)
+
+ # We should be able to export the public key from the loaded
+ # public key, but not the private key.
+ pk2.export_public(self.tname('keygen-pub2.pem'))
+ self.assertRaises(Ed25519UsageError,
+ pk2.export_private, self.tname('keygen-priv2.pem'))
+
+ def test_emit(self):
+ """Basic sanity check on the code emitters."""
+ k = Ed25519.generate()
+
+ ccode = io.StringIO()
+ k.emit_c_public(ccode)
+ self.assertIn("ed25519_pub_key", ccode.getvalue())
+ self.assertIn("ed25519_pub_key_len", ccode.getvalue())
+
+ rustcode = io.StringIO()
+ k.emit_rust_public(rustcode)
+ self.assertIn("ED25519_PUB_KEY", rustcode.getvalue())
+
+ def test_emit_pub(self):
+ """Basic sanity check on the code emitters."""
+ pubname = self.tname("public.pem")
+ k = Ed25519.generate()
+ k.export_public(pubname)
+
+ k2 = load(pubname)
+
+ ccode = io.StringIO()
+ k2.emit_c_public(ccode)
+ self.assertIn("ed25519_pub_key", ccode.getvalue())
+ self.assertIn("ed25519_pub_key_len", ccode.getvalue())
+
+ rustcode = io.StringIO()
+ k2.emit_rust_public(rustcode)
+ self.assertIn("ED25519_PUB_KEY", rustcode.getvalue())
+
+ def test_sig(self):
+ k = Ed25519.generate()
+ buf = b'This is the message'
+ sha = hashlib.sha256()
+ sha.update(buf)
+ digest = sha.digest()
+ sig = k.sign_digest(digest)
+
+ # The code doesn't have any verification, so verify this
+ # manually.
+ k.key.public_key().verify(signature=sig, data=digest)
+
+ # Modify the message to make sure the signature fails.
+ sha = hashlib.sha256()
+ sha.update(b'This is thE message')
+ new_digest = sha.digest()
+ self.assertRaises(InvalidSignature,
+ k.key.public_key().verify,
+ signature=sig,
+ data=new_digest)
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tools/mcuboot/imgtool/keys/general.py b/tools/mcuboot/imgtool/keys/general.py
new file mode 100644
index 00000000..ce7a2d26
--- /dev/null
+++ b/tools/mcuboot/imgtool/keys/general.py
@@ -0,0 +1,45 @@
+"""General key class."""
+
+import sys
+
+AUTOGEN_MESSAGE = "/* Autogenerated by imgtool.py, do not edit. */"
+
+class KeyClass(object):
+ def _emit(self, header, trailer, encoded_bytes, indent, file=sys.stdout, len_format=None):
+ print(AUTOGEN_MESSAGE, file=file)
+ print(header, end='', file=file)
+ for count, b in enumerate(encoded_bytes):
+ if count % 8 == 0:
+ print("\n" + indent, end='', file=file)
+ else:
+ print(" ", end='', file=file)
+ print("0x{:02x},".format(b), end='', file=file)
+ print("\n" + trailer, file=file)
+ if len_format is not None:
+ print(len_format.format(len(encoded_bytes)), file=file)
+
+ def emit_c_public(self, file=sys.stdout):
+ self._emit(
+ header="const unsigned char {}_pub_key[] = {{".format(self.shortname()),
+ trailer="};",
+ encoded_bytes=self.get_public_bytes(),
+ indent=" ",
+ len_format="const unsigned int {}_pub_key_len = {{}};".format(self.shortname()),
+ file=file)
+
+ def emit_rust_public(self, file=sys.stdout):
+ self._emit(
+ header="static {}_PUB_KEY: &'static [u8] = &[".format(self.shortname().upper()),
+ trailer="];",
+ encoded_bytes=self.get_public_bytes(),
+ indent=" ",
+ file=file)
+
+ def emit_private(self, minimal, file=sys.stdout):
+ self._emit(
+ header="const unsigned char enc_priv_key[] = {",
+ trailer="};",
+ encoded_bytes=self.get_private_bytes(minimal),
+ indent=" ",
+ len_format="const unsigned int enc_priv_key_len = {};",
+ file=file)
diff --git a/tools/mcuboot/imgtool/keys/rsa.py b/tools/mcuboot/imgtool/keys/rsa.py
new file mode 100644
index 00000000..f8273bf5
--- /dev/null
+++ b/tools/mcuboot/imgtool/keys/rsa.py
@@ -0,0 +1,163 @@
+"""
+RSA Key management
+"""
+
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric import rsa
+from cryptography.hazmat.primitives.asymmetric.padding import PSS, MGF1
+from cryptography.hazmat.primitives.hashes import SHA256
+
+from .general import KeyClass
+
+
+# Sizes that bootutil will recognize
+RSA_KEY_SIZES = [2048, 3072]
+
+
+class RSAUsageError(Exception):
+ pass
+
+
+class RSAPublic(KeyClass):
+ """The public key can only do a few operations"""
+ def __init__(self, key):
+ self.key = key
+
+ def key_size(self):
+ return self.key.key_size
+
+ def shortname(self):
+ return "rsa"
+
+ def _unsupported(self, name):
+ raise RSAUsageError("Operation {} requires private key".format(name))
+
+ def _get_public(self):
+ return self.key
+
+ def get_public_bytes(self):
+ # The key embedded into MCUboot is in PKCS1 format.
+ return self._get_public().public_bytes(
+ encoding=serialization.Encoding.DER,
+ format=serialization.PublicFormat.PKCS1)
+
+ def get_private_bytes(self, minimal):
+ self._unsupported('get_private_bytes')
+
+ def export_private(self, path, passwd=None):
+ self._unsupported('export_private')
+
+ def export_public(self, path):
+ """Write the public key to the given file."""
+ pem = self._get_public().public_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PublicFormat.SubjectPublicKeyInfo)
+ with open(path, 'wb') as f:
+ f.write(pem)
+
+ def sig_type(self):
+ return "PKCS1_PSS_RSA{}_SHA256".format(self.key_size())
+
+ def sig_tlv(self):
+ return"RSA{}".format(self.key_size())
+
+ def sig_len(self):
+ return self.key_size() / 8
+
+ def verify(self, signature, payload):
+ k = self.key
+ if isinstance(self.key, rsa.RSAPrivateKey):
+ k = self.key.public_key()
+ return k.verify(signature=signature, data=payload,
+ padding=PSS(mgf=MGF1(SHA256()), salt_length=32),
+ algorithm=SHA256())
+
+
+class RSA(RSAPublic):
+ """
+ Wrapper around an RSA key, with imgtool support.
+ """
+
+ def __init__(self, key):
+ """The key should be a private key from cryptography"""
+ self.key = key
+
+ @staticmethod
+ def generate(key_size=2048):
+ if key_size not in RSA_KEY_SIZES:
+ raise RSAUsageError("Key size {} is not supported by MCUboot"
+ .format(key_size))
+ pk = rsa.generate_private_key(
+ public_exponent=65537,
+ key_size=key_size,
+ backend=default_backend())
+ return RSA(pk)
+
+ def _get_public(self):
+ return self.key.public_key()
+
+ def _build_minimal_rsa_privkey(self, der):
+ '''
+ Builds a new DER that only includes N/E/D/P/Q RSA parameters;
+ standard DER private bytes provided by OpenSSL also includes
+ CRT params (DP/DQ/QP) which can be removed.
+ '''
+ OFFSET_N = 7 # N is always located at this offset
+ b = bytearray(der)
+ off = OFFSET_N
+ if b[off + 1] != 0x82:
+ raise RSAUsageError("Error parsing N while minimizing")
+ len_N = (b[off + 2] << 8) + b[off + 3] + 4
+ off += len_N
+ if b[off + 1] != 0x03:
+ raise RSAUsageError("Error parsing E while minimizing")
+ len_E = b[off + 2] + 4
+ off += len_E
+ if b[off + 1] != 0x82:
+ raise RSAUsageError("Error parsing D while minimizing")
+ len_D = (b[off + 2] << 8) + b[off + 3] + 4
+ off += len_D
+ if b[off + 1] != 0x81:
+ raise RSAUsageError("Error parsing P while minimizing")
+ len_P = b[off + 2] + 3
+ off += len_P
+ if b[off + 1] != 0x81:
+ raise RSAUsageError("Error parsing Q while minimizing")
+ len_Q = b[off + 2] + 3
+ off += len_Q
+ # adjust DER size for removed elements
+ b[2] = (off - 4) >> 8
+ b[3] = (off - 4) & 0xff
+ return b[:off]
+
+ def get_private_bytes(self, minimal):
+ priv = self.key.private_bytes(
+ encoding=serialization.Encoding.DER,
+ format=serialization.PrivateFormat.TraditionalOpenSSL,
+ encryption_algorithm=serialization.NoEncryption())
+ if minimal:
+ priv = self._build_minimal_rsa_privkey(priv)
+ return priv
+
+ def export_private(self, path, passwd=None):
+ """Write the private key to the given file, protecting it with the
+ optional password."""
+ if passwd is None:
+ enc = serialization.NoEncryption()
+ else:
+ enc = serialization.BestAvailableEncryption(passwd)
+ pem = self.key.private_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PrivateFormat.PKCS8,
+ encryption_algorithm=enc)
+ with open(path, 'wb') as f:
+ f.write(pem)
+
+ def sign(self, payload):
+ # The verification code only allows the salt length to be the
+ # same as the hash length, 32.
+ return self.key.sign(
+ data=payload,
+ padding=PSS(mgf=MGF1(SHA256()), salt_length=32),
+ algorithm=SHA256())
diff --git a/tools/mcuboot/imgtool/keys/rsa_test.py b/tools/mcuboot/imgtool/keys/rsa_test.py
new file mode 100644
index 00000000..b0afa835
--- /dev/null
+++ b/tools/mcuboot/imgtool/keys/rsa_test.py
@@ -0,0 +1,115 @@
+"""
+Tests for RSA keys
+"""
+
+import io
+import os
+import sys
+import tempfile
+import unittest
+
+from cryptography.exceptions import InvalidSignature
+from cryptography.hazmat.primitives.asymmetric.padding import PSS, MGF1
+from cryptography.hazmat.primitives.hashes import SHA256
+
+# Setup sys path so 'imgtool' is in it.
+sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__),
+ '../..')))
+
+from imgtool.keys import load, RSA, RSAUsageError
+from imgtool.keys.rsa import RSA_KEY_SIZES
+
+
+class KeyGeneration(unittest.TestCase):
+
+ def setUp(self):
+ self.test_dir = tempfile.TemporaryDirectory()
+
+ def tname(self, base):
+ return os.path.join(self.test_dir.name, base)
+
+ def tearDown(self):
+ self.test_dir.cleanup()
+
+ def test_keygen(self):
+ # Try generating a RSA key with non-supported size
+ with self.assertRaises(RSAUsageError):
+ RSA.generate(key_size=1024)
+
+ for key_size in RSA_KEY_SIZES:
+ name1 = self.tname("keygen.pem")
+ k = RSA.generate(key_size=key_size)
+ k.export_private(name1, b'secret')
+
+ # Try loading the key without a password.
+ self.assertIsNone(load(name1))
+
+ k2 = load(name1, b'secret')
+
+ pubname = self.tname('keygen-pub.pem')
+ k2.export_public(pubname)
+ pk2 = load(pubname)
+
+ # We should be able to export the public key from the loaded
+ # public key, but not the private key.
+ pk2.export_public(self.tname('keygen-pub2.pem'))
+ self.assertRaises(RSAUsageError, pk2.export_private,
+ self.tname('keygen-priv2.pem'))
+
+ def test_emit(self):
+ """Basic sanity check on the code emitters."""
+ for key_size in RSA_KEY_SIZES:
+ k = RSA.generate(key_size=key_size)
+
+ ccode = io.StringIO()
+ k.emit_c_public(ccode)
+ self.assertIn("rsa_pub_key", ccode.getvalue())
+ self.assertIn("rsa_pub_key_len", ccode.getvalue())
+
+ rustcode = io.StringIO()
+ k.emit_rust_public(rustcode)
+ self.assertIn("RSA_PUB_KEY", rustcode.getvalue())
+
+ def test_emit_pub(self):
+ """Basic sanity check on the code emitters, from public key."""
+ pubname = self.tname("public.pem")
+ for key_size in RSA_KEY_SIZES:
+ k = RSA.generate(key_size=key_size)
+ k.export_public(pubname)
+
+ k2 = load(pubname)
+
+ ccode = io.StringIO()
+ k2.emit_c_public(ccode)
+ self.assertIn("rsa_pub_key", ccode.getvalue())
+ self.assertIn("rsa_pub_key_len", ccode.getvalue())
+
+ rustcode = io.StringIO()
+ k2.emit_rust_public(rustcode)
+ self.assertIn("RSA_PUB_KEY", rustcode.getvalue())
+
+ def test_sig(self):
+ for key_size in RSA_KEY_SIZES:
+ k = RSA.generate(key_size=key_size)
+ buf = b'This is the message'
+ sig = k.sign(buf)
+
+ # The code doesn't have any verification, so verify this
+ # manually.
+ k.key.public_key().verify(
+ signature=sig,
+ data=buf,
+ padding=PSS(mgf=MGF1(SHA256()), salt_length=32),
+ algorithm=SHA256())
+
+ # Modify the message to make sure the signature fails.
+ self.assertRaises(InvalidSignature,
+ k.key.public_key().verify,
+ signature=sig,
+ data=b'This is thE message',
+ padding=PSS(mgf=MGF1(SHA256()), salt_length=32),
+ algorithm=SHA256())
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tools/mcuboot/imgtool/keys/x25519.py b/tools/mcuboot/imgtool/keys/x25519.py
new file mode 100644
index 00000000..63c0b5a7
--- /dev/null
+++ b/tools/mcuboot/imgtool/keys/x25519.py
@@ -0,0 +1,107 @@
+"""
+X25519 key management
+"""
+
+from cryptography.hazmat.backends import default_backend
+from cryptography.hazmat.primitives import serialization
+from cryptography.hazmat.primitives.asymmetric import x25519
+
+from .general import KeyClass
+
+
+class X25519UsageError(Exception):
+ pass
+
+
+class X25519Public(KeyClass):
+ def __init__(self, key):
+ self.key = key
+
+ def shortname(self):
+ return "x25519"
+
+ def _unsupported(self, name):
+ raise X25519UsageError("Operation {} requires private key".format(name))
+
+ def _get_public(self):
+ return self.key
+
+ def get_public_bytes(self):
+ # The key is embedded into MBUboot in "SubjectPublicKeyInfo" format
+ return self._get_public().public_bytes(
+ encoding=serialization.Encoding.DER,
+ format=serialization.PublicFormat.SubjectPublicKeyInfo)
+
+ def get_private_bytes(self, minimal):
+ self._unsupported('get_private_bytes')
+
+ def export_private(self, path, passwd=None):
+ self._unsupported('export_private')
+
+ def export_public(self, path):
+ """Write the public key to the given file."""
+ pem = self._get_public().public_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PublicFormat.SubjectPublicKeyInfo)
+ with open(path, 'wb') as f:
+ f.write(pem)
+
+ def sig_type(self):
+ return "X25519"
+
+ def sig_tlv(self):
+ return "X25519"
+
+ def sig_len(self):
+ return 32
+
+
+class X25519(X25519Public):
+ """
+ Wrapper around an X25519 private key.
+ """
+
+ def __init__(self, key):
+ """key should be an instance of EllipticCurvePrivateKey"""
+ self.key = key
+
+ @staticmethod
+ def generate():
+ pk = x25519.X25519PrivateKey.generate()
+ return X25519(pk)
+
+ def _get_public(self):
+ return self.key.public_key()
+
+ def get_private_bytes(self, minimal):
+ return self.key.private_bytes(
+ encoding=serialization.Encoding.DER,
+ format=serialization.PrivateFormat.PKCS8,
+ encryption_algorithm=serialization.NoEncryption())
+
+ def export_private(self, path, passwd=None):
+ """
+ Write the private key to the given file, protecting it with the
+ optional password.
+ """
+ if passwd is None:
+ enc = serialization.NoEncryption()
+ else:
+ enc = serialization.BestAvailableEncryption(passwd)
+ pem = self.key.private_bytes(
+ encoding=serialization.Encoding.PEM,
+ format=serialization.PrivateFormat.PKCS8,
+ encryption_algorithm=enc)
+ with open(path, 'wb') as f:
+ f.write(pem)
+
+ def sign_digest(self, digest):
+ """Return the actual signature"""
+ return self.key.sign(data=digest)
+
+ def verify_digest(self, signature, digest):
+ """Verify that signature is valid for given digest"""
+ k = self.key
+ if isinstance(self.key, x25519.X25519PrivateKey):
+ k = self.key.public_key()
+ return k.verify(signature=signature, data=digest)
diff --git a/tools/mcuboot/imgtool/main.py b/tools/mcuboot/imgtool/main.py
new file mode 100644
index 00000000..c93addc0
--- /dev/null
+++ b/tools/mcuboot/imgtool/main.py
@@ -0,0 +1,352 @@
+#! /usr/bin/env python3
+#
+# Copyright 2017-2020 Linaro Limited
+# Copyright 2019-2020 Arm Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import re
+import click
+import getpass
+import imgtool.keys as keys
+import sys
+from imgtool import image, imgtool_version
+from imgtool.version import decode_version
+from .keys import (
+ RSAUsageError, ECDSAUsageError, Ed25519UsageError, X25519UsageError)
+
+MIN_PYTHON_VERSION = (3, 6)
+if sys.version_info < MIN_PYTHON_VERSION:
+ sys.exit("Python %s.%s or newer is required by imgtool."
+ % MIN_PYTHON_VERSION)
+
+
+def gen_rsa2048(keyfile, passwd):
+ keys.RSA.generate().export_private(path=keyfile, passwd=passwd)
+
+
+def gen_rsa3072(keyfile, passwd):
+ keys.RSA.generate(key_size=3072).export_private(path=keyfile,
+ passwd=passwd)
+
+
+def gen_ecdsa_p256(keyfile, passwd):
+ keys.ECDSA256P1.generate().export_private(keyfile, passwd=passwd)
+
+
+def gen_ecdsa_p224(keyfile, passwd):
+ print("TODO: p-224 not yet implemented")
+
+
+def gen_ed25519(keyfile, passwd):
+ keys.Ed25519.generate().export_private(path=keyfile, passwd=passwd)
+
+
+def gen_x25519(keyfile, passwd):
+ keys.X25519.generate().export_private(path=keyfile, passwd=passwd)
+
+
+valid_langs = ['c', 'rust']
+keygens = {
+ 'rsa-2048': gen_rsa2048,
+ 'rsa-3072': gen_rsa3072,
+ 'ecdsa-p256': gen_ecdsa_p256,
+ 'ecdsa-p224': gen_ecdsa_p224,
+ 'ed25519': gen_ed25519,
+ 'x25519': gen_x25519,
+}
+
+
+def load_key(keyfile):
+ # TODO: better handling of invalid pass-phrase
+ key = keys.load(keyfile)
+ if key is not None:
+ return key
+ passwd = getpass.getpass("Enter key passphrase: ").encode('utf-8')
+ return keys.load(keyfile, passwd)
+
+
+def get_password():
+ while True:
+ passwd = getpass.getpass("Enter key passphrase: ")
+ passwd2 = getpass.getpass("Reenter passphrase: ")
+ if passwd == passwd2:
+ break
+ print("Passwords do not match, try again")
+
+ # Password must be bytes, always use UTF-8 for consistent
+ # encoding.
+ return passwd.encode('utf-8')
+
+
+@click.option('-p', '--password', is_flag=True,
+ help='Prompt for password to protect key')
+@click.option('-t', '--type', metavar='type', required=True,
+ type=click.Choice(keygens.keys()), prompt=True,
+ help='{}'.format('One of: {}'.format(', '.join(keygens.keys()))))
+@click.option('-k', '--key', metavar='filename', required=True)
+@click.command(help='Generate pub/private keypair')
+def keygen(type, key, password):
+ password = get_password() if password else None
+ keygens[type](key, password)
+
+
+@click.option('-l', '--lang', metavar='lang', default=valid_langs[0],
+ type=click.Choice(valid_langs))
+@click.option('-k', '--key', metavar='filename', required=True)
+@click.command(help='Dump public key from keypair')
+def getpub(key, lang):
+ key = load_key(key)
+ if key is None:
+ print("Invalid passphrase")
+ elif lang == 'c':
+ key.emit_c_public()
+ elif lang == 'rust':
+ key.emit_rust_public()
+ else:
+ raise ValueError("BUG: should never get here!")
+
+
+@click.option('--minimal', default=False, is_flag=True,
+ help='Reduce the size of the dumped private key to include only '
+ 'the minimum amount of data required to decrypt. This '
+ 'might require changes to the build config. Check the docs!'
+ )
+@click.option('-k', '--key', metavar='filename', required=True)
+@click.command(help='Dump private key from keypair')
+def getpriv(key, minimal):
+ key = load_key(key)
+ if key is None:
+ print("Invalid passphrase")
+ try:
+ key.emit_private(minimal)
+ except (RSAUsageError, ECDSAUsageError, Ed25519UsageError,
+ X25519UsageError) as e:
+ raise click.UsageError(e)
+
+
+@click.argument('imgfile')
+@click.option('-k', '--key', metavar='filename')
+@click.command(help="Check that signed image can be verified by given key")
+def verify(key, imgfile):
+ key = load_key(key) if key else None
+ ret, version = image.Image.verify(imgfile, key)
+ if ret == image.VerifyResult.OK:
+ print("Image was correctly validated")
+ print("Image version: {}.{}.{}+{}".format(*version))
+ return
+ elif ret == image.VerifyResult.INVALID_MAGIC:
+ print("Invalid image magic; is this an MCUboot image?")
+ elif ret == image.VerifyResult.INVALID_TLV_INFO_MAGIC:
+ print("Invalid TLV info magic; is this an MCUboot image?")
+ elif ret == image.VerifyResult.INVALID_HASH:
+ print("Image has an invalid sha256 digest")
+ elif ret == image.VerifyResult.INVALID_SIGNATURE:
+ print("No signature found for the given key")
+ else:
+ print("Unknown return code: {}".format(ret))
+ sys.exit(1)
+
+
+def validate_version(ctx, param, value):
+ try:
+ decode_version(value)
+ return value
+ except ValueError as e:
+ raise click.BadParameter("{}".format(e))
+
+
+def validate_security_counter(ctx, param, value):
+ if value is not None:
+ if value.lower() == 'auto':
+ return 'auto'
+ else:
+ try:
+ return int(value, 0)
+ except ValueError:
+ raise click.BadParameter(
+ "{} is not a valid integer. Please use code literals "
+ "prefixed with 0b/0B, 0o/0O, or 0x/0X as necessary."
+ .format(value))
+
+
+def validate_header_size(ctx, param, value):
+ min_hdr_size = image.IMAGE_HEADER_SIZE
+ if value < min_hdr_size:
+ raise click.BadParameter(
+ "Minimum value for -H/--header-size is {}".format(min_hdr_size))
+ return value
+
+
+def get_dependencies(ctx, param, value):
+ if value is not None:
+ versions = []
+ images = re.findall(r"\((\d+)", value)
+ if len(images) == 0:
+ raise click.BadParameter(
+ "Image dependency format is invalid: {}".format(value))
+ raw_versions = re.findall(r",\s*([0-9.+]+)\)", value)
+ if len(images) != len(raw_versions):
+ raise click.BadParameter(
+ '''There's a mismatch between the number of dependency images
+ and versions in: {}'''.format(value))
+ for raw_version in raw_versions:
+ try:
+ versions.append(decode_version(raw_version))
+ except ValueError as e:
+ raise click.BadParameter("{}".format(e))
+ dependencies = dict()
+ dependencies[image.DEP_IMAGES_KEY] = images
+ dependencies[image.DEP_VERSIONS_KEY] = versions
+ return dependencies
+
+
+class BasedIntParamType(click.ParamType):
+ name = 'integer'
+
+ def convert(self, value, param, ctx):
+ try:
+ return int(value, 0)
+ except ValueError:
+ self.fail('%s is not a valid integer. Please use code literals '
+ 'prefixed with 0b/0B, 0o/0O, or 0x/0X as necessary.'
+ % value, param, ctx)
+
+
+@click.argument('outfile')
+@click.argument('infile')
+@click.option('-R', '--erased-val', type=click.Choice(['0', '0xff']),
+ required=False,
+ help='The value that is read back from erased flash.')
+@click.option('-x', '--hex-addr', type=BasedIntParamType(), required=False,
+ help='Adjust address in hex output file.')
+@click.option('-L', '--load-addr', type=BasedIntParamType(), required=False,
+ help='Load address for image when it should run from RAM.')
+@click.option('--save-enctlv', default=False, is_flag=True,
+ help='When upgrading, save encrypted key TLVs instead of plain '
+ 'keys. Enable when BOOT_SWAP_SAVE_ENCTLV config option '
+ 'was set.')
+@click.option('-E', '--encrypt', metavar='filename',
+ help='Encrypt image using the provided public key')
+@click.option('-e', '--endian', type=click.Choice(['little', 'big']),
+ default='little', help="Select little or big endian")
+@click.option('--overwrite-only', default=False, is_flag=True,
+ help='Use overwrite-only instead of swap upgrades')
+@click.option('--boot-record', metavar='sw_type', help='Create CBOR encoded '
+ 'boot record TLV. The sw_type represents the role of the '
+ 'software component (e.g. CoFM for coprocessor firmware). '
+ '[max. 12 characters]')
+@click.option('-M', '--max-sectors', type=int,
+ help='When padding allow for this amount of sectors (defaults '
+ 'to 128)')
+@click.option('--confirm', default=False, is_flag=True,
+ help='When padding the image, mark it as confirmed')
+@click.option('--pad', default=False, is_flag=True,
+ help='Pad image to --slot-size bytes, adding trailer magic')
+@click.option('-S', '--slot-size', type=BasedIntParamType(), required=True,
+ help='Size of the slot where the image will be written')
+@click.option('--pad-header', default=False, is_flag=True,
+ help='Add --header-size zeroed bytes at the beginning of the '
+ 'image')
+@click.option('-H', '--header-size', callback=validate_header_size,
+ type=BasedIntParamType(), required=True)
+@click.option('--pad-sig', default=False, is_flag=True,
+ help='Add 0-2 bytes of padding to ECDSA signature '
+ '(for mcuboot <1.5)')
+@click.option('-d', '--dependencies', callback=get_dependencies,
+ required=False, help='''Add dependence on another image, format:
+ "(,), ... "''')
+@click.option('-s', '--security-counter', callback=validate_security_counter,
+ help='Specify the value of security counter. Use the `auto` '
+ 'keyword to automatically generate it from the image version.')
+@click.option('-v', '--version', callback=validate_version, required=True)
+@click.option('--align', type=click.Choice(['1', '2', '4', '8']),
+ required=True)
+@click.option('--public-key-format', type=click.Choice(['hash', 'full']),
+ default='hash', help='In what format to add the public key to '
+ 'the image manifest: full key or hash of the key.')
+@click.option('-k', '--key', metavar='filename')
+@click.command(help='''Create a signed or unsigned image\n
+ INFILE and OUTFILE are parsed as Intel HEX if the params have
+ .hex extension, otherwise binary format is used''')
+def sign(key, public_key_format, align, version, pad_sig, header_size,
+ pad_header, slot_size, pad, confirm, max_sectors, overwrite_only,
+ endian, encrypt, infile, outfile, dependencies, load_addr, hex_addr,
+ erased_val, save_enctlv, security_counter, boot_record):
+ img = image.Image(version=decode_version(version), header_size=header_size,
+ pad_header=pad_header, pad=pad, confirm=confirm,
+ align=int(align), slot_size=slot_size,
+ max_sectors=max_sectors, overwrite_only=overwrite_only,
+ endian=endian, load_addr=load_addr, erased_val=erased_val,
+ save_enctlv=save_enctlv,
+ security_counter=security_counter)
+ img.load(infile)
+ key = load_key(key) if key else None
+ enckey = load_key(encrypt) if encrypt else None
+ if enckey and key:
+ if ((isinstance(key, keys.ECDSA256P1) and
+ not isinstance(enckey, keys.ECDSA256P1Public))
+ or (isinstance(key, keys.RSA) and
+ not isinstance(enckey, keys.RSAPublic))):
+ # FIXME
+ raise click.UsageError("Signing and encryption must use the same "
+ "type of key")
+
+ if pad_sig and hasattr(key, 'pad_sig'):
+ key.pad_sig = True
+
+ img.create(key, public_key_format, enckey, dependencies, boot_record)
+ img.save(outfile, hex_addr)
+
+
+class AliasesGroup(click.Group):
+
+ _aliases = {
+ "create": "sign",
+ }
+
+ def list_commands(self, ctx):
+ cmds = [k for k in self.commands]
+ aliases = [k for k in self._aliases]
+ return sorted(cmds + aliases)
+
+ def get_command(self, ctx, cmd_name):
+ rv = click.Group.get_command(self, ctx, cmd_name)
+ if rv is not None:
+ return rv
+ if cmd_name in self._aliases:
+ return click.Group.get_command(self, ctx, self._aliases[cmd_name])
+ return None
+
+
+@click.command(help='Print imgtool version information')
+def version():
+ print(imgtool_version)
+
+
+@click.command(cls=AliasesGroup,
+ context_settings=dict(help_option_names=['-h', '--help']))
+def imgtool():
+ pass
+
+
+imgtool.add_command(keygen)
+imgtool.add_command(getpub)
+imgtool.add_command(getpriv)
+imgtool.add_command(verify)
+imgtool.add_command(sign)
+imgtool.add_command(version)
+
+
+if __name__ == '__main__':
+ imgtool()
diff --git a/tools/mcuboot/imgtool/version.py b/tools/mcuboot/imgtool/version.py
new file mode 100644
index 00000000..030b012c
--- /dev/null
+++ b/tools/mcuboot/imgtool/version.py
@@ -0,0 +1,53 @@
+# Copyright 2017 Linaro Limited
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Semi Semantic Versioning
+
+Implements a subset of semantic versioning that is supportable by the image
+header.
+"""
+
+from collections import namedtuple
+import re
+
+SemiSemVersion = namedtuple('SemiSemVersion', ['major', 'minor', 'revision',
+ 'build'])
+
+version_re = re.compile(
+ r"""^([1-9]\d*|0)(\.([1-9]\d*|0)(\.([1-9]\d*|0)(\+([1-9]\d*|0))?)?)?$""")
+
+
+def decode_version(text):
+ """Decode the version string, which should be of the form maj.min.rev+build
+ """
+ m = version_re.match(text)
+ if m:
+ result = SemiSemVersion(
+ int(m.group(1)) if m.group(1) else 0,
+ int(m.group(3)) if m.group(3) else 0,
+ int(m.group(5)) if m.group(5) else 0,
+ int(m.group(7)) if m.group(7) else 0)
+ return result
+ else:
+ msg = "Invalid version number, should be maj.min.rev+build with later "
+ msg += "parts optional"
+ raise ValueError(msg)
+
+
+if __name__ == '__main__':
+ print(decode_version("1.2"))
+ print(decode_version("1.0"))
+ print(decode_version("0.0.2+75"))
+ print(decode_version("0.0.0+00"))
diff --git a/tools/mcuboot/jgdb.sh b/tools/mcuboot/jgdb.sh
new file mode 100644
index 00000000..a79c87c6
--- /dev/null
+++ b/tools/mcuboot/jgdb.sh
@@ -0,0 +1,6 @@
+#! /bin/bash
+
+source $(dirname $0)/../target.sh
+
+# Start the jlink gdb server
+JLinkGDBServer -if swd -device $SOC -speed auto
diff --git a/tools/mcuboot/jl.sh b/tools/mcuboot/jl.sh
new file mode 100644
index 00000000..260206d5
--- /dev/null
+++ b/tools/mcuboot/jl.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+source $(dirname $0)/../target.sh
+
+JLinkExe -speed auto -si SWD -device $SOC
diff --git a/tools/mcuboot/mcubin.bt b/tools/mcuboot/mcubin.bt
new file mode 100644
index 00000000..e2ec3614
--- /dev/null
+++ b/tools/mcuboot/mcubin.bt
@@ -0,0 +1,135 @@
+// Copyright (C) 2019, Linaro Ltd
+//
+// SPDX-License-Identifier: Apache-2.0
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may
+// not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This file is a Binary Template file for the 010 Editor
+// (http://www.sweetscape.com/010editor/) to allow it to show the
+// structure of an MCUboot image.
+
+LittleEndian();
+
+struct ENTRY {
+ uint32 id;
+ uint32 offset;
+ uint32 size;
+ uint32 pad;
+};
+
+// The simulator writes the partition table at the beginning of the
+// image, so that we can tell where the partitions are. If you are
+// trying to view an image captured from a device, you can either
+// construct a synthetic partition table in the file, or change code
+// described below to hardcode one.
+struct PTABLE {
+ uchar pheader[8];
+ if (ptable.pheader != "mcuboot\0") {
+ // NOTE: Put code here to hard code a partition table, and
+ // continue.
+ Warning("Invalid magic on ptable header");
+ return -1;
+ } else {
+ uint32 count;
+ struct ENTRY entries[count];
+ }
+};
+
+struct PTABLE ptable;
+
+struct IMAGE_VERSION {
+ uchar major;
+ uchar minor;
+ uint16 revision;
+ uint32 build_num;
+};
+
+struct IHDR {
+ uint32 magic ;
+ uint32 load_addr ;
+ uint16 hdr_size ;
+ uint16 protect_size ;
+ uint32 img_size ;
+ uint32 flags;
+ struct IMAGE_VERSION ver;
+ uint32 _pad1;
+};
+
+struct TLV_HDR {
+ uint16 magic;
+ uint16 tlv_tot;
+};
+
+struct TLV {
+ uchar type ;
+ uchar pad;
+ uint16 len;
+
+ switch (type) {
+ case 0x01: // keyhash
+ uchar keyhash[len];
+ break;
+ case 0x40: // dependency
+ if (len != 12) {
+ Warning("Invalid dependency size");
+ return -1;
+ }
+ uchar image_id;
+ uchar pad1;
+ uint16 pad2;
+ struct IMAGE_VERSION version;
+ break;
+ default:
+ // Other, just consume the data.
+ uchar data[len];
+ }
+};
+
+local int i;
+local int epos;
+
+for (i = 0; i < ptable.count; i++) {
+ FSeek(ptable.entries[i].offset);
+ switch (ptable.entries[i].id) {
+ case 1:
+ case 2:
+ case 4:
+ case 5:
+ struct IMAGE {
+ struct IHDR ihdr;
+
+ if (ihdr.magic == 0x96f3b83d) {
+ uchar payload[ihdr.img_size];
+
+ epos = FTell();
+ struct TLV_HDR tlv_hdr;
+
+ if (tlv_hdr.magic == 0x6907) {
+ epos += tlv_hdr.tlv_tot;
+ while (FTell() < epos) {
+ struct TLV tlv;
+ }
+ }
+ }
+ // uchar block[ptable.entries[i].size];
+ } image;
+ break;
+ case 3:
+ struct SCRATCH {
+ uchar data[ptable.entries[i].size];
+ } scratch;
+ break;
+ default:
+ break;
+ }
+}
diff --git a/tools/mcuboot/requirements.txt b/tools/mcuboot/requirements.txt
new file mode 100644
index 00000000..9481e2c1
--- /dev/null
+++ b/tools/mcuboot/requirements.txt
@@ -0,0 +1,4 @@
+cryptography>=2.6
+intelhex
+click
+cbor>=1.0.0
diff --git a/tools/mcuboot/setup.py b/tools/mcuboot/setup.py
new file mode 100644
index 00000000..058d0cb4
--- /dev/null
+++ b/tools/mcuboot/setup.py
@@ -0,0 +1,29 @@
+import setuptools
+from imgtool import imgtool_version
+
+setuptools.setup(
+ name="imgtool",
+ version=imgtool_version,
+ author="The MCUboot committers",
+ author_email="dev-mcuboot@lists.runtime.co",
+ description=("MCUboot's image signing and key management"),
+ license="Apache Software License",
+ url="http://github.com/JuulLabs-OSS/mcuboot",
+ packages=setuptools.find_packages(),
+ python_requires='>=3.6',
+ install_requires=[
+ 'cryptography>=2.4.2',
+ 'intelhex>=2.2.1',
+ 'click',
+ 'cbor>=1.0.0',
+ ],
+ entry_points={
+ "console_scripts": ["imgtool=imgtool.main:imgtool"]
+ },
+ classifiers=[
+ "Programming Language :: Python :: 3",
+ "Development Status :: 4 - Beta",
+ "Topic :: Software Development :: Build Tools",
+ "License :: OSI Approved :: Apache Software License",
+ ],
+)
diff --git a/tools/rle_encode.py b/tools/rle_encode.py
new file mode 100644
index 00000000..80a0926f
--- /dev/null
+++ b/tools/rle_encode.py
@@ -0,0 +1,379 @@
+#!/usr/bin/env python3
+
+# SPDX-License-Identifier: LGPL-3.0-or-later
+# Copyright (C) 2020 Daniel Thompson
+
+import argparse
+import sys
+import os.path
+from PIL import Image
+
+def clut8_rgb888(i):
+ """Reference CLUT for wasp-os.
+
+ Technically speaking this is not a CLUT because the we lookup the colours
+ algorithmically to avoid the cost of a genuine CLUT. The palette is
+ designed to be fairly easy to generate algorithmically.
+
+ The palette includes all 216 web-safe colours together 4 grays and
+ 36 additional colours that target "gaps" at the brighter end of the web
+ safe set. There are 11 greys (plus black and white) although two are
+ fairly close together.
+
+ :param int i: Index (from 0..255 inclusive) into the CLUT
+ :return: 24-bit colour in RGB888 format
+ """
+ if i < 216:
+ rgb888 = ( i % 6) * 0x33
+ rg = i // 6
+ rgb888 += (rg % 6) * 0x3300
+ rgb888 += (rg // 6) * 0x330000
+ elif i < 252:
+ i -= 216
+ rgb888 = 0x7f + (( i % 3) * 0x33)
+ rg = i // 3
+ rgb888 += 0x4c00 + ((rg % 4) * 0x3300)
+ rgb888 += 0x7f0000 + ((rg // 4) * 0x330000)
+ else:
+ i -= 252
+ rgb888 = 0x2c2c2c + (0x101010 * i)
+
+ return rgb888
+
+def clut8_rgb565(i):
+ """RBG565 CLUT for wasp-os.
+
+ This CLUT implements the same palette as :py:meth:`clut8_888` but
+ outputs RGB565 pixels.
+
+ .. note::
+
+ This function is unused within this file but needs to be
+ maintained alongside the reference clut so it is reproduced
+ here.
+
+ :param int i: Index (from 0..255 inclusive) into the CLUT
+ :return: 16-bit colour in RGB565 format
+ """
+ if i < 216:
+ rgb565 = (( i % 6) * 0x33) >> 3
+ rg = i // 6
+ rgb565 += ((rg % 6) * (0x33 << 3)) & 0x07e0
+ rgb565 += ((rg // 6) * (0x33 << 8)) & 0xf800
+ elif i < 252:
+ i -= 216
+ rgb565 = (0x7f + (( i % 3) * 0x33)) >> 3
+ rg = i // 3
+ rgb565 += ((0x4c << 3) + ((rg % 4) * (0x33 << 3))) & 0x07e0
+ rgb565 += ((0x7f << 8) + ((rg // 4) * (0x33 << 8))) & 0xf800
+ else:
+ i -= 252
+ gr6 = (0x2c + (0x10 * i)) >> 2
+ gr5 = gr6 >> 1
+ rgb565 = (gr5 << 11) + (gr6 << 5) + gr5
+
+ return rgb565
+
+class ReverseCLUT:
+ def __init__(self, clut):
+ l = []
+ for i in range(256):
+ l.append(clut(i))
+ self.clut = tuple(l)
+ self.lookup = {}
+
+ def __call__(self, rgb888):
+ """Compare rgb888 to every element of the CLUT and pick the
+ closest match.
+ """
+ if rgb888 in self.lookup:
+ return self.lookup[rgb888]
+
+ best = 200000
+ index = -1
+ clut = self.clut
+ r = rgb888 >> 16
+ g = (rgb888 >> 8) & 0xff
+ b = rgb888 & 0xff
+
+ for i in range(256):
+ candidate = clut[i]
+ rd = r - (candidate >> 16)
+ gd = g - ((candidate >> 8) & 0xff)
+ bd = b - (candidate & 0xff)
+ # This is the Euclidian distance (squared)
+ distance = rd * rd + gd * gd + bd * bd
+ if distance < best:
+ best = distance
+ index = i
+
+ self.lookup[rgb888] = index
+ #print(f'# #{rgb888:06x} -> #{clut8_rgb888(index):06x}')
+ return index
+
+def varname(p):
+ return os.path.basename(os.path.splitext(p)[0])
+
+def encode(im):
+ pixels = im.load()
+
+ rle = []
+ rl = 0
+ px = pixels[0, 0]
+
+ def encode_pixel(px, rl):
+ while rl > 255:
+ rle.append(255)
+ rle.append(0)
+ rl -= 255
+ rle.append(rl)
+
+ for y in range(im.height):
+ for x in range(im.width):
+ newpx = pixels[x, y]
+ if newpx == px:
+ rl += 1
+ assert(rl < (1 << 21))
+ continue
+
+ # Code the previous run
+ encode_pixel(px, rl)
+
+ # Start a new run
+ rl = 1
+ px = newpx
+
+ # Handle the final run
+ encode_pixel(px, rl)
+
+ return (im.width, im.height, bytes(rle))
+
+def encode_2bit(im):
+ """2-bit palette based RLE encoder.
+
+ This encoder has a reprogrammable 2-bit palette. This allows it to encode
+ arbitrary images with a full 8-bit depth but the 2-byte overhead each time
+ a new colour is introduced means it is not efficient unless the image is
+ carefully constructed to keep a good locality of reference for the three
+ non-background colours.
+
+ The encoding competes well with the 1-bit encoder for small monochrome
+ images but once run-lengths longer than 62 start to become frequent then
+ this encoding is about 30% larger than a 1-bit encoding.
+ """
+ pixels = im.load()
+ assert(im.width <= 255)
+ assert(im.height <= 255)
+
+ full_palette = ReverseCLUT(clut8_rgb888)
+
+ rle = []
+ rl = 0
+ px = pixels[0, 0]
+ # black, grey25, grey50, white
+ palette = [0, 254, 219, 215]
+ next_color = 1
+
+ def encode_pixel(px, rl):
+ nonlocal next_color
+ px = full_palette((px[0] << 16) + (px[1] << 8) + px[2])
+ if px not in palette:
+ rle.append(next_color << 6)
+ rle.append(px)
+ palette[next_color] = px
+ next_color += 1
+ if next_color >= len(palette):
+ next_color = 1
+ px = palette.index(px)
+ if rl >= 63:
+ rle.append((px << 6) + 63)
+ rl -= 63
+ while rl >= 255:
+ rle.append(255)
+ rl -= 255
+ rle.append(rl)
+ else:
+ rle.append((px << 6) + rl)
+
+ # Issue the descriptor
+ rle.append(2)
+ rle.append(im.width)
+ rle.append(im.height)
+
+ for y in range(im.height):
+ for x in range(im.width):
+ newpx = pixels[x, y]
+ if newpx == px:
+ rl += 1
+ assert(rl < (1 << 21))
+ continue
+
+ # Code the previous run
+ encode_pixel(px, rl)
+
+ # Start a new run
+ rl = 1
+ px = newpx
+
+ # Handle the final run
+ encode_pixel(px, rl)
+
+ return bytes(rle)
+
+def encode_8bit(im):
+ """Experimental 8-bit RLE encoder.
+
+ For monochrome images this is about 3x less efficient than the 1-bit
+ encoder. This encoder is not currently used anywhere in wasp-os and
+ currently there is no decoder either (so don't assume this code
+ actually works).
+ """
+ pixels = im.load()
+
+ rle = []
+ rl = 0
+ px = pixels[0, 0]
+
+ def encode_pixel(px, rl):
+ px = (px[0] & 0xe0) | ((px[1] & 0xe0) >> 3) | ((px[2] & 0xc0) >> 6)
+
+ rle.append(px)
+ if rl > 0:
+ rle.append(px)
+ rl -= 2
+ if rl > (1 << 14):
+ rle.append(0x80 | ((rl >> 14) & 0x7f))
+ if rl > (1 << 7):
+ rle.append(0x80 | ((rl >> 7) & 0x7f))
+ if rl >= 0:
+ rle.append( rl & 0x7f )
+
+ for y in range(im.height):
+ for x in range(im.width):
+ newpx = pixels[x, y]
+ if newpx == px:
+ rl += 1
+ assert(rl < (1 << 21))
+ continue
+
+ # Code the previous run
+ encode_pixel(px, rl)
+
+ # Start a new run
+ rl = 1
+ px = newpx
+
+ # Handle the final run
+ encode_pixel(px, rl)
+
+ return (im.width, im.height, bytes(rle))
+
+def render_c(image, fname, indent, depth):
+ extra_indent = ' ' * indent
+ if len(image) == 3:
+ print(f'{extra_indent}// {depth}-bit RLE, generated from {fname}, '
+ f'{len(image[2])} bytes')
+ (x, y, pixels) = image
+ else:
+ print(f'{extra_indent}// {depth}-bit RLE, generated from {fname}, '
+ f'{len(image)} bytes')
+ pixels = image
+
+ print(f'{extra_indent}static const uint8_t {varname(fname)}[] = {{')
+ print(f'{extra_indent} ', end='')
+ i = 0
+ for rl in pixels:
+ print(f' {hex(rl)},', end='')
+
+ i += 1
+ if i == 12:
+ print(f'\n{extra_indent} ', end='')
+ i = 0
+ print('\n};')
+
+def render_py(image, fname, indent, depth):
+ extra_indent = ' ' * indent
+ if len(image) == 3:
+ print(f'{extra_indent}# {depth}-bit RLE, generated from {fname}, '
+ f'{len(image[2])} bytes')
+ (x, y, pixels) = image
+ print(f'{extra_indent}{varname(fname)} = (')
+ print(f'{extra_indent} {x}, {y},')
+ else:
+ print(f'{extra_indent}# {depth}-bit RLE, generated from {fname}, '
+ f'{len(image)} bytes')
+ pixels = image[3:]
+ print(f'{extra_indent}{varname(fname)} = (')
+ print(f'{extra_indent} {image[0:1]}')
+ print(f'{extra_indent} {image[1:3]}')
+
+ # Split the bytestring to ensure each line is short enough to
+ # be absorbed on the target if needed.
+ for i in range(0, len(pixels), 16):
+ print(f'{extra_indent} {pixels[i:i+16]}')
+ print(f'{extra_indent})')
+
+
+def decode_to_ascii(image):
+ (sx, sy, rle) = image
+ data = bytearray(2*sx)
+ dp = 0
+ black = ord('#')
+ white = ord(' ')
+ color = black
+
+ for rl in rle:
+ while rl:
+ data[dp] = color
+ data[dp+1] = color
+ dp += 2
+ rl -= 1
+
+ if dp >= (2*sx):
+ print(data.decode('utf-8'))
+ dp = 0
+
+ if color == black:
+ color = white
+ else:
+ color = black
+
+ # Check the image is the correct length
+ assert(dp == 0)
+
+parser = argparse.ArgumentParser(description='RLE encoder tool.')
+parser.add_argument('files', nargs='+',
+ help='files to be encoded')
+parser.add_argument('--ascii', action='store_true',
+ help='Run the resulting image(s) through an ascii art decoder')
+parser.add_argument('--c', action='store_true',
+ help='Render the output as C instead of python')
+parser.add_argument('--indent', default=0, type=int,
+ help='Add extra indentation in the generated code')
+parser.add_argument('--2bit', action='store_true', dest='twobit',
+ help='Generate 2-bit image')
+parser.add_argument('--8bit', action='store_true', dest='eightbit',
+ help='Generate 8-bit image')
+
+args = parser.parse_args()
+if args.eightbit:
+ encoder = encode_8bit
+ depth = 8
+elif args.twobit:
+ encoder = encode_2bit
+ depth = 2
+else:
+ encoder = encode
+ depth =1
+
+for fname in args.files:
+ image = encoder(Image.open(fname))
+
+ if args.c:
+ render_c(image, fname, args.indent, depth)
+ else:
+ render_py(image, fname, args.indent, depth)
+
+ if args.ascii:
+ print()
+ decode_to_ascii(image)
\ No newline at end of file