aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--doc/BLEFS.md167
-rw-r--r--doc/MemoryAnalysis.md22
-rw-r--r--doc/NavigationService.md4
-rw-r--r--doc/PinetimeStubWithNrf52DK.md12
-rw-r--r--doc/SPI-LCD-driver.md2
-rw-r--r--doc/SWD.md4
-rw-r--r--doc/ble.md14
-rw-r--r--doc/branches.md4
-rw-r--r--doc/buildAndProgram.md4
-rw-r--r--doc/code/Apps.md6
-rw-r--r--doc/code/Intro.md4
-rw-r--r--doc/contribute.md2
-rw-r--r--doc/gettingStarted/about-software.md10
-rw-r--r--doc/gettingStarted/gettingStarted-1.0.md2
-rw-r--r--doc/gettingStarted/ota-gadgetbridge.md2
-rw-r--r--doc/openOCD.md10
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/components/ble/BatteryInformationService.cpp2
-rw-r--r--src/components/ble/FSService.cpp330
-rw-r--r--src/components/ble/FSService.h191
-rw-r--r--src/components/ble/NimbleController.cpp10
-rw-r--r--src/components/ble/NimbleController.h2
-rw-r--r--src/components/fs/FS.cpp97
-rw-r--r--src/components/fs/FS.h61
-rw-r--r--src/displayapp/DisplayApp.cpp1
-rw-r--r--src/displayapp/screens/InfiniPaint.cpp2
-rw-r--r--src/displayapp/screens/WatchFaceDigital.cpp2
-rw-r--r--src/systemtask/Messages.h2
-rw-r--r--src/systemtask/SystemTask.cpp13
30 files changed, 862 insertions, 127 deletions
diff --git a/.gitignore b/.gitignore
index 39fb672b..0474017b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,3 +43,7 @@ Testing/Temporary/
#VSCODE
.vscode/.cortex-debug.registers.state.json
.vscode/.cortex-debug.peripherals.state.json
+
+#build files
+src/nRF5_SDK_15.3.0_59ac345
+src/arm-none-eabi
diff --git a/doc/BLEFS.md b/doc/BLEFS.md
new file mode 100644
index 00000000..519d84a9
--- /dev/null
+++ b/doc/BLEFS.md
@@ -0,0 +1,167 @@
+# BLE FS
+---
+
+The BLE FS protocol in InfiniTime is mostly Adafruit's BLE file transfer protocol, as described in [adafruit/Adafruit_CircuitPython_BLE_File_Transfer](https://github.com/adafruit/Adafruit_CircuitPython_BLE_File_Transfer). There are some deviations, such as the status codes. These will be described later in the document.
+
+---
+
+## UUIDs
+
+There are two relevant UUIDs in this protocol: the version characteristic, and the raw transfer characteristic.
+
+### Version
+
+UUID: `adaf0100-4669-6c65-5472-616e73666572`
+
+The version characteristic returns the version of the protocol to which the sender adheres. It returns a single unsigned 32-bit integer. The latest version at the time of writing this is 4.
+
+### Transfer
+
+UUID: `adaf0200-4669-6c65-5472-616e73666572`
+
+The transfer characteristic is responsible for all the data transfer between the client and the watch. It supports write and notify. Writing a packet on the characteristic results in a response via notify.
+
+---
+
+## Usage
+
+The separator for paths is `/`, and absolute paths must start with `/`.
+
+All of the following commands and responses are transferred via the transfer characteristic
+
+### Read file
+
+To begin reading a file, a header must first be sent. The header packet should be formatted like so:
+
+- Command (single byte): `0x10`
+- 1 byte of padding
+- Unsigned 16-bit integer encoding the length of the file path.
+- Unsigned 32-bit integer encoding the location at which to start reading the first chunk.
+- Unsigned 32-bit integer encoding the amount of bytes to be read.
+- File path: UTF-8 encoded string that is _not_ null terminated.
+
+To continue reading the file after this initial packet, the following packet should be sent until all the data has been received. No close command is required after the data has been received.
+
+- Command (single byte): `0x12`
+- Status: `0x01`
+- 2 bytes of padding
+- Unsigned 32-bit integer encoding the location at which to start reading the next chunk.
+- Unsigned 32-bit integer encoding the amount of bytes to be read. This may be different from the size in the header.
+
+Both of these commands receive the following response:
+
+- Command (single byte): `0x11`
+- Status (signed 8-bit integer)
+- 2 bytes of padding
+- Unsigned 32-bit integer encoding the offset of this chunk
+- Unsigned 32-bit integer encoding the total size of the file
+- Unsigned 32-bit integer encoding the amount of data in the current chunk
+- Contents of the current chunk
+
+### Write file
+
+To begin writing to a file, a header must first be sent. The header packet should be formatted like so:
+
+- Command (single byte): `0x20`
+- 1 byte of padding
+- Unsigned 16-bit integer encoding the length of the file path.
+- Unsigned 32-bit integer encoding the location at which to start writing to the file.
+- Unsigned 64-bit integer encoding the unix timestamp with nanosecond resolution. This will be used as the modification time. At the time of writing, this is not implemented in InfiniTime, but may be in the future.
+- Unsigned 32-bit integer encoding the size of the file that will be sent
+- File path: UTF-8 encoded string that is _not_ null terminated.
+
+To continue reading the file after this initial packet, the following packet should be sent until all the data has been sent and a response had been received with 0 free space. No close command is required after the data has been received.
+
+- Command (single byte): `0x22`
+- Status: `0x01`
+- 2 bytes of padding.
+- Unsigned 32-bit integer encoding the location at which to write the next chunk.
+- Unsigned 32-bit integer encoding the amount of bytes to be written.
+- Data
+
+Both of these commands receive the following response:
+
+- Command (single byte): `0x21`
+- Status (signed 8-bit integer)
+- 2 bytes of padding
+- Unsigned 32-bit integer encoding the current offset in the file
+- Unsigned 64-bit integer encoding the unix timestamp with nanosecond resolution. This will be used as the modification time. At the time of writing, this is not implemented in InfiniTime, but may be in the future.
+- Unsigned 32-bit integer encoding the amount of data the client can send until the file is full.
+
+### Delete file
+
+- Command (single byte): `0x30`
+- 1 byte of padding
+- Unsigned 16-bit integer encoding the length of the file path.
+- File path: UTF-8 encoded string that is _not_ null terminated.
+
+The response to this packet will be as follows:
+
+- Command (single byte): `0x31`
+- Status (signed 8-bit integer)
+
+### Make directory
+
+- Command (single byte): `0x40`
+- 1 byte of padding
+- Unsigned 16-bit integer encoding the length of the file path.
+- 4 bytes of padding
+- Unsigned 64-bit integer encoding the unix timestamp with nanosecond resolution.
+- File path: UTF-8 encoded string that is _not_ null terminated.
+
+The response to this packet will be as follows:
+
+- Command (single byte): `0x41`
+- Status (signed 8-bit integer)
+- 6 bytes of padding
+- Unsigned 64-bit integer encoding the unix timestamp with nanosecond resolution.
+
+### List directory
+
+Paths returned by this command are relative to the path given in the request
+
+- Command (single byte): `0x50`
+- 1 byte of padding
+- Unsigned 16-bit integer encoding the length of the file path.
+- File path: UTF-8 encoded string that is _not_ null terminated.
+
+The response to this packet will be as follows. Responses will be sent until the final entry, which will have entry number == total entries
+
+- Command (single byte): `0x51`
+- Status (signed 8-bit integer)
+- Unsigned 16-bit integer encoding the length of the file path.
+- Unsigned 32-bit integer encoding the entry number
+- Unsigned 32-bit integer encoding the total amount of entries
+- Flags: unsigned 32-bit integer
+ + Bit 0: Set when entry is a directory
+ + Bits 1-7: Reserved
+- Unsigned 64-bit integer encoding the unix timestamp of the modification time with nanosecond resolution
+- Unsigned 32-bit integer encoding the size of the file
+- Path: UTF-8 encoded string that is _not_ null terminated.
+
+### Move file or directory
+
+- Command (single byte): `0x60`
+- 1 byte of padding
+- Unsigned 16-bit integer encoding the length of the old path
+- Unsigned 16-bit integer encoding the length of the new path
+- Old path: UTF-8 encoded string that is _not_ null terminated.
+- 1 byte of padding
+- Newpath: UTF-8 encoded string that is _not_ null terminated.
+
+The response to this packet will be as follows:
+
+- Command (single byte): `0x61`
+- Status (signed 8-bit integer)
+
+---
+
+## Deviations
+
+This section describes the differences between Adafruit's spec and InfiniTime's implementation.
+
+### Status codes
+
+The status codes returned by InfiniTime are a signed 8-bit integer, rather than an unsigned one as described in the spec.
+
+InfiniTime uses LittleFS error codes rather than the ones described in the spec. Those codes can be found in [lfs.h](https://github.com/littlefs-project/littlefs/blob/master/lfs.h#L70). \ No newline at end of file
diff --git a/doc/MemoryAnalysis.md b/doc/MemoryAnalysis.md
index 7304e3f3..376f98f6 100644
--- a/doc/MemoryAnalysis.md
+++ b/doc/MemoryAnalysis.md
@@ -32,13 +32,13 @@ In this analysis, I used [Linkermapviz](https://github.com/PromyLOPh/linkermapvi
### Linkermapviz
-[Linkermapviz](https://github.com/PromyLOPh/linkermapviz) parses the MAP file and displays its content in a graphical way into an HTML page:
+[Linkermapviz](https://github.com/PromyLOPh/linkermapviz) parses the MAP file and displays its content on an HTML page as a graphic:
![linkermapviz](./memoryAnalysis/linkermapviz.png)
-Using this tool, you can easily see the size of each symbol relative to the other one, and check what is using most of the space,...
+Using this tool, you can compare the relative size of symbols. This can be helpful for checking memory usage at a glance.
-Also, as Linkermapviz is written in Python, you can easily modify it to adapt it to your firmware, export data in another format,... For example, [I modified it to parse the contents of the MAP file and export it in a CSV file](https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-842338620). I could later on open this file in LibreOffice Calc and use sort/filter functionality to search for specific symbols in specific files...
+Also, as Linkermapviz is written in Python, you can easily modify and adapt it to your firmware or export data in another format. For example, [here it is modified to parse the contents of the MAP file and export it in a CSV file](https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-842338620). This file could later be opened in LibreOffice Calc where sort/filter functionality could be used to search for specific symbols in specific files...
### Puncover
[Puncover](https://github.com/HBehrens/puncover) is another useful tools that analyses the binary file generated by the compiler (the .out file that contains all debug information). It provides valuable information about the symbols (data and code): name, position, size, max stack of each functions, callers, callees...
@@ -46,8 +46,8 @@ Also, as Linkermapviz is written in Python, you can easily modify it to adapt it
Puncover is really easy to install:
- - clone the repo and cd into the cloned directory
- - setup a venv
+ - Clone the repo and cd into the cloned directory
+ - Setup a venv
- `python -m virtualenv venv`
- `source venv/bin/activate`
- Install : `pip install .`
@@ -60,13 +60,13 @@ Puncover is really easy to install:
- Launch a browser at http://localhost:5000/
### Analysis
-Using the MAP file and tools, we can easily see what symbols are using most of the FLASH memory space. In this case, with no surprise, fonts and graphics are the biggest flash space consumer.
+Using the MAP file and tools, we can easily see what symbols are using most of the flash memory. In this case, unsuprisingly, fonts and graphics are the largest use of flash memory.
![Puncover](./memoryAnalysis/puncover-all-symbols.png)
-This way, you can easily check what needs to be optimized : we should find a way to store big static data (like fonts and graphics) in the external flash memory, for example.
+This way, you can easily check what needs to be optimized. We should find a way to store big static data (like fonts and graphics) in the external flash memory, for example.
-It's always a good idea to check the flash memory space when working on the project : this way, you can easily check that your developments are using a reasonable amount of space.
+It's always a good idea to check the flash memory space when working on the project. This way, you can easily check that your developments are using a reasonable amount of space.
### Links
- Analysis with linkermapviz : https://github.com/InfiniTimeOrg/InfiniTime/issues/313#issuecomment-842338620
@@ -210,7 +210,7 @@ NRF_LOG_INFO("heap : %d", m.uordblks);
```
#### Analysis
-According to my experimentation, InfiniTime uses ~6000bytes of heap most of the time. Except when the Navigation app is launched, where the heap usage increases to... more than 9500 bytes (meaning that the heap overflows and could potentially corrupt the stack!!!). This is a bug that should be fixed in #362.
+According to my experimentation, InfiniTime uses ~6000bytes of heap most of the time. Except when the Navigation app is launched, where the heap usage exceeds 9500 bytes (meaning that the heap overflows and could potentially corrupt the stack). This is a bug that should be fixed in #362.
To know exactly what's consuming heap memory, you can `wrap` functions like `malloc()` into your own functions. In this wrapper, you can add logging code or put breakpoints:
@@ -245,7 +245,7 @@ Using this technique, I was able to trace all malloc calls at boot (boot -> digi
- https://www.embedded.com/mastering-stack-and-heap-for-system-reliability-part-3-avoiding-heap-errors/
## LVGL
-I did a deep analysis of the usage of the buffer dedicated for lvgl (managed by lv_mem).
+I did a deep analysis of the usage of the buffer dedicated to lvgl (managed by lv_mem).
This buffer is used by lvgl to allocated memory for drivers (display/touch), screens, themes, and all widgets created by the apps.
The usage of this buffer can be monitored using this code :
@@ -256,7 +256,7 @@ lv_mem_monitor(&mon);
NRF_LOG_INFO("\t Free %d / %d -- max %d", mon.free_size, mon.total_size, mon.max_used);
```
-The most interesting metric is `mon.max_used` which specifies the maximum number of bytes that were used from this buffer since the initialization of lvgl.
+The most interesting metric is `mon.max_used` which specifies the maximum number of bytes used from this buffer since the initialization of lvgl.
According to my measurements, initializing the theme, display/touch driver and screens cost **4752** bytes!
Then, initializing the digital clock face costs **1541 bytes**.
For example a simple lv_label needs **~140 bytes** of memory.
diff --git a/doc/NavigationService.md b/doc/NavigationService.md
index fd81d0bf..5a4f69e0 100644
--- a/doc/NavigationService.md
+++ b/doc/NavigationService.md
@@ -1,6 +1,6 @@
# Navigation Service
## Introduction
-The navigation ble service provides 4 characteristics to allow the the watch to display navigation instructions from a companion application. The intended purpose is when performing some outdoor activities, for example running or cycling.
+The navigation ble service provides 4 characteristics to allow the watch to display navigation instructions from a companion application. This service is intended to be used when performing some outdoor activities, for example running or cycling.
The 4 characteristics are:
flag (string) - Upcoming icon name
@@ -22,7 +22,7 @@ This is a client supplied string describing the upcoming instruction such as "At
This is a short string describing the distance to the upcoming instruction such as "50 m".
## Progress (UUID 00010004-78fc-48fe-8e23-433b3a1942d0)
-The percent complete in a uint8. The watch displays this as an overall progress in a progress bar.
+The percent complete in a uint8. The watch displays this as an overall progress in a progress bar.
## Full icon list
* arrive
diff --git a/doc/PinetimeStubWithNrf52DK.md b/doc/PinetimeStubWithNrf52DK.md
index c4857921..dcaad69b 100644
--- a/doc/PinetimeStubWithNrf52DK.md
+++ b/doc/PinetimeStubWithNrf52DK.md
@@ -1,11 +1,11 @@
# Build a stub for PineTime using NRF52-DK
-[NRF52-DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52-DK) is the official developpment kit for NRF52832 SoC from Nordic Semiconductor.
+[NRF52-DK](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52-DK) is the official developpment kit for the NRF52832 SoC from Nordic Semiconductor used in the PineTime.
-It can be very useful for PineTime development:
- * You can use it embedded JLink SWD programmer/debugger to program and debug you code on the PineTime
- * As it's based on the same SoC than the PineTime, you can program it to actually run the same code than the PineTime.
+This development kit can be very useful for PineTime development:
+ * You can use its embedded JLink SWD programmer/debugger to program and debug your code on the PineTime
+ * As it's based on the same SoC than the PineTime, you can program it to actually run the same code as the PineTime.
-This page is about the 2nd point : we will build a stub that will allow us to run the same code than the one you could run on the PineTime. This will allow you to work more easily if you don't have a PineTime dev kit around, if you don't want to modify your dev kit for SWD programming, or if you want to use some feature from the DK (like power measurement).
+This page is about the 2nd point. We will build a stub that will allow us to run the same code you can run on the PineTime. This will allow you to work more easily if you don't have a PineTime dev kit around, if you don't want to modify your dev kit for SWD programming, or if you want to use some feature from the NRF52-DK (like power measurement).
This stub only implements the display, the button and the BLE radio. The other features from the pintime are missing:
* heart rate sensor
@@ -41,7 +41,7 @@ You just need to make the following connections:
| P0.13 | Button IN (D3 in my case) |
| GND | GND |
-You also need to enable the I/O expander to disconnect pins from buttons and led on the NRF52-DK and leave them available on the pin headers:
+You also need to enable the I/O expander to disconnect pins from the buttons and LED on the NRF52-DK and leave them available on the pin headers:
| NRF52 -DK | NRF52- DK |
| --------- | --------- |
diff --git a/doc/SPI-LCD-driver.md b/doc/SPI-LCD-driver.md
index f787aab7..29f3bbfa 100644
--- a/doc/SPI-LCD-driver.md
+++ b/doc/SPI-LCD-driver.md
@@ -1,6 +1,6 @@
# The SPI LCD driver
## Introduction
-The LCD controller that drive the display of the Pinetime is the Sitronix ST7789V. This controller is easy to integrate with an MCU thanks to its SPI interface, and has some interesting features like:
+The LCD controller that drives the display of the Pinetime is the [Sitronix ST7789V](https://wiki.pine64.org/images/5/54/ST7789V_v1.6.pdf). This controller is easy to integrate with an MCU thanks to its SPI interface, and has some interesting features like:
- an on-chip display data RAM that can store the whole framebuffer
- partial screen update
- hardware assisted vertical scrolling
diff --git a/doc/SWD.md b/doc/SWD.md
index 4146e6ae..155983b3 100644
--- a/doc/SWD.md
+++ b/doc/SWD.md
@@ -4,9 +4,9 @@ Download the files **bootloader.bin**, **image-x.y.z.bin** and **pinetime-graphi
![Image file](imageFile.png)
The bootloader reads a boot logo from the external SPI flash memory. The first step consists of flashing a tool in the MCU that will flash the boot logo into this SPI flash memory. This first step is optional but recommended (the bootloader will display garbage on screen for a few second if you don't do it).
-Using your SWD tool, flash **pinetime-graphics-x.y.z.bin** at offset **0x0000**. Reset the MCU and wait for a few second, until the logo is completely drawn on the display.
+Using your SWD tool, flash **pinetime-graphics-x.y.z.bin** at offset **0x0000**. Reset the MCU and wait for a few seconds until the logo is completely drawn on the display.
-Then, using your SWD tool, flash those file at specific offset:
+Then, using your SWD tool, flash these file at the following offsets:
- bootloader.bin : **0x0000**
- image-x.y.z.bin : **0x8000**
diff --git a/doc/ble.md b/doc/ble.md
index 2b86243e..d2502636 100644
--- a/doc/ble.md
+++ b/doc/ble.md
@@ -9,6 +9,7 @@ This page describes the BLE implementation and API built in this firmware.
### Table of Contents
- [BLE Connection](#ble-connection)
+- [BLE FS](#ble-fs)
- [BLE UUIDs](#ble-uuids)
- [BLE Services](#ble-services)
- [CTS](#cts)
@@ -51,6 +52,13 @@ If **CTS** is detected, it'll request the current time to the companion applicat
---
+## BLE FS
+
+The documentation for BLE FS can be found here:
+[BLEFS.md](./BLEFS.md)
+
+---
+
## BLE UUIDs
When possible, InfiniTime tries to implement BLE services defined by the BLE specification.
@@ -112,11 +120,11 @@ Reading a value from the firmware version characteristic will yield a UTF-8 enco
#### Battery Level
-Reading from the battery level characteristic yields a single byte of data. This byte can be converted to an unsigned 8-bit integer which will be the battery percentage. This characteristic allows notify for updates as the value changes.
+Reading from the battery level characteristic yields a single byte of data. This byte can be converted to an unsigned 8-bit integer which will be the battery percentage. This characteristic allows notifications for updates as the value changes.
#### Heart Rate
-Reading from the heart rate characteristic yields two bytes of data. I am not sure of the function of the first byte. It appears to always be zero. The second byte can be converted to an unsigned 8-bit integer which is the current heart rate. This characteristic also allows notify for updates as the value changes.
+Reading from the heart rate characteristic yields two bytes of data. I am not sure of the function of the first byte. It appears to always be zero. The second byte can be converted to an unsigned 8-bit integer which is the current heart rate. This characteristic also allows notifications for updates as the value changes.
---
@@ -289,4 +297,4 @@ This characteristic expects a particular format:
- Microsecond divided by `1e6*256` (`uint8`)
- Binary 0001 (`uint8`)
-Write all of these together, encoded as little-endian, to the current time characteristic. \ No newline at end of file
+Write all of these together, encoded as little-endian, to the current time characteristic.
diff --git a/doc/branches.md b/doc/branches.md
index ef280f40..3c86375f 100644
--- a/doc/branches.md
+++ b/doc/branches.md
@@ -1,7 +1,7 @@
# Branches
The branching model of this project is based on the workflow named [Git flow](https://nvie.com/posts/a-successful-git-branching-model/).
-It is based on 2 main branches:
+The project is based on 2 main branches:
- **master** : this branch is always ready to be deployed. It means that at any time, we should be able to build the branch and release a new version of the application.
- **develop** : this branch contains the latest development that will be integrated in the next release once it's considered as stable.
@@ -9,4 +9,4 @@ New features should be implemented in **feature branches** created from **develo
To release a new version of the application, when develop is considered stable, a **release** branch is created from **develop**. This can be considered as a *release candidate* branch. When everything is OK, this release branch is merged into **master** and the release is generated (a tag is applied to git, the release note is finalized, binaries are built,...) from **master**.
-Git flow also supports the creation of **hotfix** branches when a bug is discovered in a released version. The **hotfix** branch is created from **master** and will be used only to implement a fix to this bug. Multiple hotfix branches can be created for the same release if more than one bugs are discovered. \ No newline at end of file
+Git flow also supports the creation of **hotfix** branches when a bug is discovered in a released version. The **hotfix** branch is created from **master** and will be used only to implement a fix to this bug. Multiple hotfix branches can be created for the same release if multiple bugs are discovered. \ No newline at end of file
diff --git a/doc/buildAndProgram.md b/doc/buildAndProgram.md
index 3686871a..feef9f6d 100644
--- a/doc/buildAndProgram.md
+++ b/doc/buildAndProgram.md
@@ -4,7 +4,7 @@ To build this project, you'll need:
- A cross-compiler : [ARM-GCC (9-2020-q2-update)](https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnu-toolchain/gnu-rm/downloads/9-2020-q2-update)
- The NRF52 SDK 15.3.0 : [nRF-SDK v15.3.0](https://developer.nordicsemi.com/nRF5_SDK/nRF5_SDK_v15.x.x/nRF5_SDK_15.3.0_59ac345.zip)
- The Python 3 modules `cbor`, `intelhex`, `click` and `cryptography` modules for the `mcuboot` tool (see [requirements.txt](../tools/mcuboot/requirements.txt))
- - To to keep the system clean a python virtual environment (`venv`) can be used to install the python modules into
+ - To keep the system clean, you can install python modules into a python virtual environment (`venv`)
```sh
python -m venv .venv
source .venv/bin/activate
@@ -260,4 +260,4 @@ Finally, merge them together with **mergehex**:
This file must be flashed at offset **0x00** of the internal memory of the NRF52832.
#### spinor.bin
-This file is the MCUBoot image of the last stable version of the recovery firmware. It must be flashed at offset **0x00** of the external SPINOR flash memory. \ No newline at end of file
+This file is the MCUBoot image of the last stable version of the recovery firmware. It must be flashed at offset **0x00** of the external SPINOR flash memory.
diff --git a/doc/code/Apps.md b/doc/code/Apps.md
index b1c7d20e..f067b58b 100644
--- a/doc/code/Apps.md
+++ b/doc/code/Apps.md
@@ -8,8 +8,8 @@ This page will teach you:
The user interface of InfiniTime is made up of **screens**.
Screens that are opened from the app launcher are considered **apps**.
Every app in InfiniTime is it's own class.
-An instance of the class is created when the app is launched and destroyed when the user exits the app.
-They run inside the "displayapp" task (briefly discussed [here](./Intro.md)).
+An instance of the class is created when the app is launched, and destroyed when the user exits the app.
+Apps run inside the "displayapp" task (briefly discussed [here](./Intro.md)).
Apps are responsible for everything drawn on the screen when they are running.
By default, apps only do something (as in a function is executed) when they are created or when a touch event is detected.
@@ -21,7 +21,7 @@ A destructor is needed to clean up LVGL and restore any changes (for example re-
App classes can override `bool OnButtonPushed()`, `bool OnTouchEvent(TouchEvents event)` and `bool OnTouchEvent(uint16_t x, uint16_t y)` to implement their own functionality for those events.
If an app only needs to display some text and do something upon a touch screen button press,
it does not need to override any of these functions, as LVGL can also handle touch events for you.
-If you have any doubts, you can always look at how the other apps are doing things.
+If you have any doubts, you can always look at how the other apps function for reference.
### Continuous updating
If your app needs to be updated continuously, you can do so by overriding the `Refresh()` function in your class
diff --git a/doc/code/Intro.md b/doc/code/Intro.md
index bf68c7a5..23b3ade1 100644
--- a/doc/code/Intro.md
+++ b/doc/code/Intro.md
@@ -24,9 +24,9 @@ There are also other tasks that are responsible for Bluetooth ("ll" and "ble" in
and periodic tasks like heartrate measurements ([heartratetask/HeartRateTask.cpp](/src/heartratetask/HeartRateTask.cpp)).
While it is possible for you to create your own task when you need it, it is recommended to just add functionality to `SystemTask::Work()` if possible.
-If you absolutely need to create another task, try to guess how much [stack space](https://www.freertos.org/FAQMem.html#StackSize) (in words/4-byte packets)
+If you absolutely need to create another task, try to estimate how much [stack space](https://www.freertos.org/FAQMem.html#StackSize) (in words/4-byte packets)
it will need instead of just typing in a large-ish number.
-You can use the define `configMINIMAL_STACK_SIZE` which is currently set to 120 words.
+You can use `configMINIMAL_STACK_SIZE` which is currently set to 120 words.
## Controllers
Controllers in InfiniTime are singleton objects that can provide access to certain resources to apps.
diff --git a/doc/contribute.md b/doc/contribute.md
index b1be84a4..f2a4aeaa 100644
--- a/doc/contribute.md
+++ b/doc/contribute.md
@@ -46,7 +46,7 @@ Other contributors can post comments about the pull request, maybe ask for more
Once the pull request is reviewed and accepted, it'll be merged into **develop** and will be released in the next version of the firmware.
-## Why all these rules?
+## Why all these rules?
Reviewing pull requests is a **very time consuming task**. Everything you do to make reviewing easier will **get your PR merged faster**.
diff --git a/doc/gettingStarted/about-software.md b/doc/gettingStarted/about-software.md
index b19a610f..e935d938 100644
--- a/doc/gettingStarted/about-software.md
+++ b/doc/gettingStarted/about-software.md
@@ -12,15 +12,15 @@ InfiniTime has three distinct firmwares:
**OTA** (**O**ver **T**he **A**ir) refers to updating of the firmware over BLE (**B**luetooth **L**ow **E**nergy). This is a functionality that allows the user to update the firmware on their device wirelessly.
-**DFU** (**D**evice **F**irmware **U**pdate) is the file format and protocol used to send the update of the firmware to the watch over-the-air. InfiniTime implement the (legacy) DFU protocol from Nordic Semiconductor (NRF).
+**DFU** (**D**evice **F**irmware **U**pdate) is the file format and protocol used to send the update of the firmware to the watch over-the-air. InfiniTime implements the (legacy) DFU protocol from Nordic Semiconductor (NRF).
## Bootloader
-Most of the time, the bootloader just runs without your intervention (update and load the firmware).
+Most of the time, the bootloader just runs without your intervention (updating and loading the firmware).
-However, you can enable 2 functionalities using the push button:
+However, you can use the bootloader to rollback to the previous firmware, or load the recovery firmware using the push button:
- - Push the button until the pine cone is drawn in **blue** to force the rollback of the previous version of the firmware, even if you've already validated the updated one
- - Push the button until the pine cone is drawn in **red** to load the recovery firmware. This recovery firmware only provides BLE connectivity and OTA functionality.
+ - Press and hold the button until the pine cone is drawn in **blue** to force the rollback of the previous version of the firmware, even if you've already validated the current one.
+ - Press and hold the button until the pine cone is drawn in **red** to load the recovery firmware. This recovery firmware only provides BLE connectivity and OTA functionality.
More info about the bootloader in [its project page](https://github.com/JF002/pinetime-mcuboot-bootloader/blob/master/README.md).
diff --git a/doc/gettingStarted/gettingStarted-1.0.md b/doc/gettingStarted/gettingStarted-1.0.md
index 30b8bdb0..890164fe 100644
--- a/doc/gettingStarted/gettingStarted-1.0.md
+++ b/doc/gettingStarted/gettingStarted-1.0.md
@@ -18,7 +18,7 @@ You can sync the time using companion apps.
You can also set the time in the settings without a companion app. (version >1.7.0)
-InfiniTime doesn't handle daylight savings automatically, so make sure to set the correct the time or sync it with a companion app
+InfiniTime doesn't handle daylight savings automatically, so make sure to set the correct the time or sync it with a companion app.
### Digital watch face
diff --git a/doc/gettingStarted/ota-gadgetbridge.md b/doc/gettingStarted/ota-gadgetbridge.md
index 022b5e4d..fe26c03b 100644
--- a/doc/gettingStarted/ota-gadgetbridge.md
+++ b/doc/gettingStarted/ota-gadgetbridge.md
@@ -18,7 +18,7 @@ Now that Gadgetbridge is connected to your PineTime, use a file browser applicat
![Gadgetbridge 3](gadgetbridge3.jpg)
-Read carefully the warning and tap **Install**:
+Read the warning carefully and tap **Install**:
![Gadgetbridge 4](gadgetbridge4.jpg)
diff --git a/doc/openOCD.md b/doc/openOCD.md
index b3661cee..df24b30b 100644
--- a/doc/openOCD.md
+++ b/doc/openOCD.md
@@ -1,12 +1,12 @@
# OpenOCD and STLink
OpenOCD (**Open O**n **C**hip **D**ebugger) is an open source tool that interfaces with many SWD/JTAG debugger to provide debugging and *in-system* programming for embedded target devices.
-It supports the **NRF52** (the CPU of the PineTime) and the **STLinkV2**, a cheap SWD debugger.
+OpenOCD supports the **NRF52** (the CPU of the PineTime) and the **STLinkV2**, a cheap SWD debugger.
-It works on X86 computers, as well as ARM/ARM64 computers and SBC (like the RaspberryPi and Pine64 Pinebook Pro) !
+OpenOCD works on X86 computers, ARM/ARM64 computers, and SBCs (like the RaspberryPi and Pine64 Pinebook Pro)!
## Installation
-We will build OpenOCD from sources, as packages from Linux distributions are most of the time outdated and do not support the NRF52 correctly.
+We will build OpenOCD from sources, as packages from Linux distributions are most of the time outdated and do not support the NRF52 properly.
- Fetch the sources from GIT, and build and install it:
@@ -27,7 +27,7 @@ sudo cp contrib/60-openocd.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
```
- - You can now plug your STLinkV2 in a USB port and run OpenOCD to see if it's working correctly:
+ - You can now plug your STLinkV2 into a USB port and run OpenOCD to see if it's working correctly:
```
$ openocd -f interface/stlink.cfg -f target/nrf52.cfg
@@ -63,7 +63,7 @@ gdb_breakpoint_override hard
source [find target/nrf52.cfg]
```
-This file specifies to OpenOCD which debugger and target it will be connected to..
+This file specifies to OpenOCD which debugger and target it will be connected to.
Then, we use various *user files* to use OpenOCD to flash InfiniTime binary files.
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a9e9d859..47da4a8a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -486,6 +486,7 @@ list(APPEND SOURCE_FILES
components/ble/NavigationService.cpp
displayapp/fonts/lv_font_navi_80.c
components/ble/BatteryInformationService.cpp
+ components/ble/FSService.cpp
components/ble/ImmediateAlertService.cpp
components/ble/ServiceDiscovery.cpp
components/ble/HeartRateService.cpp
@@ -556,6 +557,7 @@ list(APPEND RECOVERY_SOURCE_FILES
components/ble/MusicService.cpp
components/ble/weather/WeatherService.cpp
components/ble/BatteryInformationService.cpp
+ components/ble/FSService.cpp
components/ble/ImmediateAlertService.cpp
components/ble/ServiceDiscovery.cpp
components/ble/NavigationService.cpp
@@ -668,6 +670,7 @@ set(INCLUDE_FILES
components/ble/DfuService.h
components/firmwarevalidator/FirmwareValidator.h
components/ble/BatteryInformationService.h
+ components/ble/FSService.h
components/ble/ImmediateAlertService.h
components/ble/ServiceDiscovery.h
components/ble/BleClient.h
diff --git a/src/components/ble/BatteryInformationService.cpp b/src/components/ble/BatteryInformationService.cpp
index 82df7b15..9a3f86f5 100644
--- a/src/components/ble/BatteryInformationService.cpp
+++ b/src/components/ble/BatteryInformationService.cpp
@@ -17,7 +17,7 @@ BatteryInformationService::BatteryInformationService(Controllers::Battery& batte
characteristicDefinition {{.uuid = &batteryLevelUuid.u,
.access_cb = BatteryInformationServiceCallback,
.arg = this,
- .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_READ_AUTHEN | BLE_GATT_CHR_F_NOTIFY,
+ .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
.val_handle = &batteryLevelHandle},
{0}},
serviceDefinition {
diff --git a/src/components/ble/FSService.cpp b/src/components/ble/FSService.cpp
new file mode 100644
index 00000000..8dc9ed67
--- /dev/null
+++ b/src/components/ble/FSService.cpp
@@ -0,0 +1,330 @@
+#include <nrf_log.h>
+#include "FSService.h"
+#include "components/ble/BleController.h"
+#include "systemtask/SystemTask.h"
+
+using namespace Pinetime::Controllers;
+
+constexpr ble_uuid16_t FSService::fsServiceUuid;
+constexpr ble_uuid128_t FSService::fsVersionUuid;
+constexpr ble_uuid128_t FSService::fsTransferUuid;
+
+int FSServiceCallback(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt* ctxt, void* arg) {
+ auto* fsService = static_cast<FSService*>(arg);
+ return fsService->OnFSServiceRequested(conn_handle, attr_handle, ctxt);
+}
+
+FSService::FSService(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::FS& fs)
+ : systemTask {systemTask},
+ fs {fs},
+ characteristicDefinition {{.uuid = &fsVersionUuid.u,
+ .access_cb = FSServiceCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_READ,
+ .val_handle = &versionCharacteristicHandle},
+ {
+ .uuid = &fsTransferUuid.u,
+ .access_cb = FSServiceCallback,
+ .arg = this,
+ .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
+ .val_handle = &transferCharacteristicHandle,
+ },
+ {0}},
+ serviceDefinition {
+ {/* Device Information Service */
+ .type = BLE_GATT_SVC_TYPE_PRIMARY,
+ .uuid = &fsServiceUuid.u,
+ .characteristics = characteristicDefinition},
+ {0},
+ } {
+}
+
+void FSService::Init() {
+ int res = 0;
+ res = ble_gatts_count_cfg(serviceDefinition);
+ ASSERT(res == 0);
+
+ res = ble_gatts_add_svcs(serviceDefinition);
+ ASSERT(res == 0);
+}
+
+int FSService::OnFSServiceRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context) {
+ if (attributeHandle == versionCharacteristicHandle) {
+ NRF_LOG_INFO("FS_S : handle = %d", versionCharacteristicHandle);
+ int res = os_mbuf_append(context->om, &fsVersion, sizeof(fsVersion));
+ return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
+ }
+ if (attributeHandle == transferCharacteristicHandle) {
+ return FSCommandHandler(connectionHandle, context->om);
+ }
+ return 0;
+}
+
+int FSService::FSCommandHandler(uint16_t connectionHandle, os_mbuf* om) {
+ auto command = static_cast<commands>(om->om_data[0]);
+ NRF_LOG_INFO("[FS_S] -> FSCommandHandler Command %d", command);
+ // Just always make sure we are awake...
+ systemTask.PushMessage(Pinetime::System::Messages::StartFileTransfer);
+ vTaskDelay(10);
+ while (systemTask.IsSleeping()) {
+ vTaskDelay(100); // 50ms
+ }
+ lfs_dir_t dir = {0};
+ lfs_info info = {0};
+ lfs_file f = {0};
+ switch (command) {
+ case commands::READ: {
+ NRF_LOG_INFO("[FS_S] -> Read");
+ auto* header = (ReadHeader*) om->om_data;
+ uint16_t plen = header->pathlen;
+ if (plen > maxpathlen) { //> counts for null term
+ return -1;
+ }
+ memcpy(filepath, header->pathstr, plen);
+ filepath[plen] = 0; // Copy and null teminate string
+ ReadResponse resp;
+ os_mbuf* om;
+ resp.command = commands::READ_DATA;
+ resp.status = 0x01;
+ resp.chunkoff = header->chunkoff;
+ int res = fs.Stat(filepath, &info);
+ if (res == LFS_ERR_NOENT && info.type != LFS_TYPE_DIR) {
+ resp.status = (int8_t) res;
+ resp.chunklen = 0;
+ resp.totallen = 0;
+ om = ble_hs_mbuf_from_flat(&resp, sizeof(ReadResponse));
+ } else {
+ resp.chunklen = std::min(header->chunksize, info.size); // TODO add mtu somehow
+ resp.totallen = info.size;
+ fs.FileOpen(&f, filepath, LFS_O_RDONLY);
+ fs.FileSeek(&f, header->chunkoff);
+ uint8_t fileData[resp.chunklen] = {0};
+ resp.chunklen = fs.FileRead(&f, fileData, resp.chunklen);
+ om = ble_hs_mbuf_from_flat(&resp, sizeof(ReadResponse));
+ os_mbuf_append(om, fileData, resp.chunklen);
+ fs.FileClose(&f);
+ }
+
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ break;
+ }
+ case commands::READ_PACING: {
+ NRF_LOG_INFO("[FS_S] -> Readpacing");
+ auto* header = (ReadHeader*) om->om_data;
+ ReadResponse resp;
+ resp.command = commands::READ_DATA;
+ resp.status = 0x01;
+ resp.chunkoff = header->chunkoff;
+ int res = fs.Stat(filepath, &info);
+ if (res == LFS_ERR_NOENT && info.type != LFS_TYPE_DIR) {
+ resp.status = (int8_t) res;
+ resp.chunklen = 0;
+ resp.totallen = 0;
+ } else {
+ resp.chunklen = std::min(header->chunksize, info.size); // TODO add mtu somehow
+ resp.totallen = info.size;
+ fs.FileOpen(&f, filepath, LFS_O_RDONLY);
+ fs.FileSeek(&f, header->chunkoff);
+ }
+ os_mbuf* om;
+ if (resp.chunklen > 0) {
+ uint8_t fileData[resp.chunklen] = {0};
+ resp.chunklen = fs.FileRead(&f, fileData, resp.chunklen);
+ om = ble_hs_mbuf_from_flat(&resp, sizeof(ReadResponse));
+ os_mbuf_append(om, fileData, resp.chunklen);
+ } else {
+ resp.chunklen = 0;
+ om = ble_hs_mbuf_from_flat(&resp, sizeof(ReadResponse));
+ }
+ fs.FileClose(&f);
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ break;
+ }
+ case commands::WRITE: {
+ NRF_LOG_INFO("[FS_S] -> Write");
+ auto* header = (WriteHeader*) om->om_data;
+ uint16_t plen = header->pathlen;
+ if (plen > maxpathlen) { //> counts for null term
+ return -1; // TODO make this actually return a BLE notif
+ }
+ memcpy(filepath, header->pathstr, plen);
+ filepath[plen] = 0; // Copy and null teminate string
+ fileSize = header->totalSize;
+ WriteResponse resp;
+ resp.command = commands::WRITE_PACING;
+ resp.offset = header->offset;
+ resp.modTime = 0;
+
+ int res = fs.FileOpen(&f, filepath, LFS_O_RDWR | LFS_O_CREAT);
+ if (res == 0) {
+ fs.FileClose(&f);
+ resp.status = (res == 0) ? 0x01 : (int8_t) res;
+ }
+ resp.freespace = std::min(fs.getSize() - (fs.GetFSSize() * fs.getBlockSize()), fileSize - header->offset);
+ auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(WriteResponse));
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ break;
+ }
+ case commands::WRITE_DATA: {
+ NRF_LOG_INFO("[FS_S] -> WriteData");
+ auto* header = (WritePacing*) om->om_data;
+ WriteResponse resp;
+ resp.command = commands::WRITE_PACING;
+ resp.offset = header->offset;
+ int res = 0;
+
+ if (!(res = fs.FileOpen(&f, filepath, LFS_O_RDWR | LFS_O_CREAT))) {
+ if ((res = fs.FileSeek(&f, header->offset)) >= 0) {
+ res = fs.FileWrite(&f, header->data, header->dataSize);
+ }
+ fs.FileClose(&f);
+ }
+ if (res < 0) {
+ resp.status = (int8_t) res;
+ }
+ resp.freespace = std::min(fs.getSize() - (fs.GetFSSize() * fs.getBlockSize()), fileSize - header->offset);
+ auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(WriteResponse));
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ break;
+ }
+ case commands::DELETE: {
+ NRF_LOG_INFO("[FS_S] -> Delete");
+ auto* header = (DelHeader*) om->om_data;
+ uint16_t plen = header->pathlen;
+ char path[plen + 1] = {0};
+ memcpy(path, header->pathstr, plen);
+ path[plen] = 0; // Copy and null teminate string
+ DelResponse resp {};
+ resp.command = commands::DELETE_STATUS;
+ int res = fs.FileDelete(path);
+ resp.status = (res == 0) ? 0x01 : (int8_t) res;
+ auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(DelResponse));
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ break;
+ }
+ case commands::MKDIR: {
+ NRF_LOG_INFO("[FS_S] -> MKDir");
+ auto* header = (MKDirHeader*) om->om_data;
+ uint16_t plen = header->pathlen;
+ char path[plen + 1] = {0};
+ memcpy(path, header->pathstr, plen);
+ path[plen] = 0; // Copy and null teminate string
+ MKDirResponse resp {};
+ resp.command = commands::MKDIR_STATUS;
+ resp.modification_time = 0;
+ int res = fs.DirCreate(path);
+ resp.status = (res == 0) ? 0x01 : (int8_t) res;
+ auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(MKDirResponse));
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ break;
+ }
+ case commands::LISTDIR: {
+ NRF_LOG_INFO("[FS_S] -> ListDir");
+ ListDirHeader* header = (ListDirHeader*) om->om_data;
+ uint16_t plen = header->pathlen;
+ char path[plen + 1] = {0};
+ path[plen] = 0; // Copy and null teminate string
+ memcpy(path, header->pathstr, plen);
+
+ ListDirResponse resp {};
+
+ resp.command = commands::LISTDIR_ENTRY;
+ resp.status = 0x01;
+ resp.totalentries = 0;
+ resp.entry = 0;
+ resp.modification_time = 0;
+ int res = fs.DirOpen(path, &dir);
+ if (res != 0) {
+ resp.status = (int8_t) res;
+ auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(ListDirResponse));
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ break;
+ };
+ while (fs.DirRead(&dir, &info)) {
+ resp.totalentries++;
+ }
+ fs.DirRewind(&dir);
+ while (true) {
+ res = fs.DirRead(&dir, &info);
+ if (res <= 0) {
+ break;
+ }
+ switch (info.type) {
+ case LFS_TYPE_REG: {
+ resp.flags = 0;
+ resp.file_size = info.size;
+ break;
+ }
+ case LFS_TYPE_DIR: {
+ resp.flags = 1;
+ resp.file_size = 0;
+ break;
+ }
+ }
+
+ // strcpy(resp.path, info.name);
+ resp.path_length = strlen(info.name);
+ auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(ListDirResponse));
+ os_mbuf_append(om, info.name, resp.path_length);
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ /*
+ * Todo Figure out how to know when the previous Notify was TX'd
+ * For now just delay 100ms to make sure that the data went out...
+ */
+ vTaskDelay(100); // Allow stuff to actually go out over the BLE conn
+ resp.entry++;
+ }
+ assert(fs.DirClose(&dir) == 0);
+ resp.file_size = 0;
+ resp.path_length = 0;
+ resp.flags = 0;
+ auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(ListDirResponse));
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ break;
+ }
+ case commands::MOVE: {
+ NRF_LOG_INFO("[FS_S] -> Move");
+ MoveHeader* header = (MoveHeader*) om->om_data;
+ uint16_t plen = header->OldPathLength;
+ // Null Terminate string
+ header->pathstr[plen] = 0;
+ char path[header->NewPathLength + 1] = {0};
+ memcpy(path, &header->pathstr[plen + 1], header->NewPathLength);
+ path[header->NewPathLength] = 0; // Copy and null teminate string
+ MoveResponse resp {};
+ resp.command = commands::MOVE_STATUS;
+ int8_t res = (int8_t) fs.Rename(header->pathstr, path);
+ resp.status = (res == 0) ? 1 : res;
+ auto* om = ble_hs_mbuf_from_flat(&resp, sizeof(MoveResponse));
+ ble_gattc_notify_custom(connectionHandle, transferCharacteristicHandle, om);
+ }
+ default:
+ break;
+ }
+ NRF_LOG_INFO("[FS_S] -> done ");
+ systemTask.PushMessage(Pinetime::System::Messages::StopFileTransfer);
+ return 0;
+}
+
+// Loads resp with file data given a valid filepath header and resp
+void FSService::prepareReadDataResp(ReadHeader* header, ReadResponse* resp) {
+ // uint16_t plen = header->pathlen;
+ resp->command = commands::READ_DATA;
+ resp->chunkoff = header->chunkoff;
+ resp->status = 0x01;
+ struct lfs_info info = {};
+ int res = fs.Stat(filepath, &info);
+ if (res == LFS_ERR_NOENT && info.type != LFS_TYPE_DIR) {
+ resp->status = 0x03;
+ resp->chunklen = 0;
+ resp->totallen = 0;
+ } else {
+ lfs_file f;
+ resp->chunklen = std::min(header->chunksize, info.size);
+ resp->totallen = info.size;
+ fs.FileOpen(&f, filepath, LFS_O_RDONLY);
+ fs.FileSeek(&f, header->chunkoff);
+ resp->chunklen = fs.FileRead(&f, resp->chunk, resp->chunklen);
+ fs.FileClose(&f);
+ }
+}
diff --git a/src/components/ble/FSService.h b/src/components/ble/FSService.h
new file mode 100644
index 00000000..828925a8
--- /dev/null
+++ b/src/components/ble/FSService.h
@@ -0,0 +1,191 @@
+#pragma once
+#define min // workaround: nimble's min/max macros conflict with libstdc++
+#define max
+#include <host/ble_gap.h>
+#undef max
+#undef min
+
+#include "components/fs/FS.h"
+
+namespace Pinetime {
+ namespace System {
+ class SystemTask;
+ }
+ namespace Controllers {
+ class Ble;
+ class FSService {
+ public:
+ FSService(Pinetime::System::SystemTask& systemTask, Pinetime::Controllers::FS& fs);
+ void Init();
+
+ int OnFSServiceRequested(uint16_t connectionHandle, uint16_t attributeHandle, ble_gatt_access_ctxt* context);
+ void NotifyFSRaw(uint16_t connectionHandle);
+
+ private:
+ Pinetime::System::SystemTask& systemTask;
+ Pinetime::Controllers::FS& fs;
+ static constexpr uint16_t FSServiceId {0xFEBB};
+ static constexpr uint16_t fsVersionId {0x0100};
+ static constexpr uint16_t fsTransferId {0x0200};
+ uint16_t fsVersion = {0x0004};
+ static constexpr uint16_t maxpathlen = 256;
+ static constexpr ble_uuid16_t fsServiceUuid {
+ .u {.type = BLE_UUID_TYPE_16},
+ .value = {0xFEBB}}; // {0x72, 0x65, 0x66, 0x73, 0x6e, 0x61, 0x72, 0x54, 0x65, 0x6c, 0x69, 0x46, 0xBB, 0xFE, 0xAF, 0xAD}};
+
+ static constexpr ble_uuid128_t fsVersionUuid {
+ .u {.type = BLE_UUID_TYPE_128},
+ .value = {0x72, 0x65, 0x66, 0x73, 0x6e, 0x61, 0x72, 0x54, 0x65, 0x6c, 0x69, 0x46, 0x00, 0x01, 0xAF, 0xAD}};
+
+ static constexpr ble_uuid128_t fsTransferUuid {
+ .u {.type = BLE_UUID_TYPE_128},
+ .value = {0x72, 0x65, 0x66, 0x73, 0x6e, 0x61, 0x72, 0x54, 0x65, 0x6c, 0x69, 0x46, 0x00, 0x02, 0xAF, 0xAD}};
+
+ struct ble_gatt_chr_def characteristicDefinition[3];
+ struct ble_gatt_svc_def serviceDefinition[2];
+ uint16_t versionCharacteristicHandle;
+ uint16_t transferCharacteristicHandle;
+
+ enum class commands : uint8_t {
+ INVALID = 0x00,
+ READ = 0x10,
+ READ_DATA = 0x11,
+ READ_PACING = 0x12,
+ WRITE = 0x20,
+ WRITE_PACING = 0x21,
+ WRITE_DATA = 0x22,
+ DELETE = 0x30,
+ DELETE_STATUS = 0x31,
+ MKDIR = 0x40,
+ MKDIR_STATUS = 0x41,
+ LISTDIR = 0x50,
+ LISTDIR_ENTRY = 0x51,
+ MOVE = 0x60,
+ MOVE_STATUS = 0x61
+ };
+ enum class FSState : uint8_t {
+ IDLE = 0x00,
+ READ = 0x01,
+ WRITE = 0x02,
+ };
+ FSState state;
+ char filepath[maxpathlen]; // TODO ..ugh fixed filepath len
+ int fileSize;
+ using ReadHeader = struct __attribute__((packed)) {
+ commands command;
+ uint8_t padding;
+ uint16_t pathlen;
+ uint32_t chunkoff;
+ uint32_t chunksize;
+ char pathstr[];
+ };
+
+ using ReadResponse = struct __attribute__((packed)) {
+ commands command;
+ uint8_t status;
+ uint16_t padding;
+ uint32_t chunkoff;
+ uint32_t totallen;
+ uint32_t chunklen;
+ uint8_t chunk[];
+ };
+ using ReadPacing = struct __attribute__((packed)) {
+ commands command;
+ uint8_t status;
+ uint16_t padding;
+ uint32_t chunkoff;
+ uint32_t chunksize;
+ };
+
+ using WriteHeader = struct __attribute__((packed)) {
+ commands command;
+ uint8_t padding;
+ uint16_t pathlen;
+ uint32_t offset;
+ uint64_t modTime;
+ uint32_t totalSize;
+ char pathstr[];
+ };
+
+ using WriteResponse = struct __attribute__((packed)) {
+ commands command;
+ uint8_t status;
+ uint16_t padding;
+ uint32_t offset;
+ uint64_t modTime;
+ uint32_t freespace;
+ };
+
+ using WritePacing = struct __attribute__((packed)) {
+ commands command;
+ uint8_t status;
+ uint16_t padding;
+ uint32_t offset;
+ uint32_t dataSize;
+ uint8_t data[];
+ };
+ using ListDirHeader = struct __attribute__((packed)) {
+ commands command;
+ uint8_t padding;
+ uint16_t pathlen;
+ char pathstr[];
+ };
+
+ using ListDirResponse = struct __attribute__((packed)) {
+ commands command;
+ uint8_t status;
+ uint16_t path_length;
+ uint32_t entry;
+ uint32_t totalentries;
+ uint32_t flags;
+ uint64_t modification_time;
+ uint32_t file_size;
+ char path[];
+ };
+
+ using MKDirHeader = struct __attribute__((packed)) {
+ commands command;
+ uint8_t padding;
+ uint16_t pathlen;
+ uint32_t padding2;
+ uint64_t time;
+ char pathstr[];
+ };
+
+ using MKDirResponse = struct __attribute__((packed)) {
+ commands command;
+ uint8_t status;
+ uint32_t padding1;
+ uint16_t padding2;
+ uint64_t modification_time;
+ };
+
+ using DelHeader = struct __attribute__((packed)) {
+ commands command;
+ uint8_t padding;
+ uint16_t pathlen;
+ char pathstr[];
+ };
+
+ using DelResponse = struct __attribute__((packed)) {
+ commands command;
+ uint8_t status;
+ };
+ using MoveHeader = struct __attribute__((packed)) {
+ commands command;
+ uint8_t padding;
+ uint16_t OldPathLength;
+ uint16_t NewPathLength;
+ char pathstr[];
+ };
+
+ using MoveResponse = struct __attribute__((packed)) {
+ commands command;
+ uint8_t status;
+ };
+
+ int FSCommandHandler(uint16_t connectionHandle, os_mbuf* om);
+ void prepareReadDataResp(ReadHeader* header, ReadResponse* resp);
+ };
+ }
+}
diff --git a/src/components/ble/NimbleController.cpp b/src/components/ble/NimbleController.cpp
index acf4f94b..d8510bd3 100644
--- a/src/components/ble/NimbleController.cpp
+++ b/src/components/ble/NimbleController.cpp
@@ -30,7 +30,7 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
Pinetime::Drivers::SpiNorFlash& spiNorFlash,
Controllers::HeartRateController& heartRateController,
Controllers::MotionController& motionController,
- Pinetime::Controllers::FS& fs)
+ Controllers::FS& fs)
: systemTask {systemTask},
bleController {bleController},
dateTimeController {dateTimeController},
@@ -50,6 +50,7 @@ NimbleController::NimbleController(Pinetime::System::SystemTask& systemTask,
immediateAlertService {systemTask, notificationManager},
heartRateService {systemTask, heartRateController},
motionService {systemTask, motionController},
+ fsService {systemTask, fs},
serviceDiscovery({&currentTimeClient, &alertNotificationClient}) {
}
@@ -97,6 +98,7 @@ void NimbleController::Init() {
immediateAlertService.Init();
heartRateService.Init();
motionService.Init();
+ fsService.Init();
int rc;
rc = ble_hs_util_ensure_addr(0);
@@ -132,9 +134,7 @@ void NimbleController::Init() {
RestoreBond();
- if (!ble_gap_adv_active() && !bleController.IsConnected()) {
- StartAdvertising();
- }
+ StartAdvertising();
}
void NimbleController::StartAdvertising() {
@@ -272,7 +272,7 @@ int NimbleController::OnGAPEvent(ble_gap_event* event) {
* display capability only so we only handle the "display" action here.
*
* Standards insist that the rand() PRNG be deterministic.
- * Use the nimble TRNG here since rand() is predictable.
+ * Use the tinycrypt prng here since rand() is predictable.
*/
NRF_LOG_INFO("Security event : BLE_GAP_EVENT_PASSKEY_ACTION");
if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
diff --git a/src/components/ble/NimbleController.h b/src/components/ble/NimbleController.h
index 12bd6924..2b300e63 100644
--- a/src/components/ble/NimbleController.h
+++ b/src/components/ble/NimbleController.h
@@ -22,6 +22,7 @@
#include "components/ble/MotionService.h"
#include "components/ble/weather/WeatherService.h"
#include "components/fs/FS.h"
+#include "components/ble/FSService.h"
namespace Pinetime {
namespace Drivers {
@@ -109,6 +110,7 @@ namespace Pinetime {
ImmediateAlertService immediateAlertService;
HeartRateService heartRateService;
MotionService motionService;
+ FSService fsService;
ServiceDiscovery serviceDiscovery;
uint8_t addrType;
diff --git a/src/components/fs/FS.cpp b/src/components/fs/FS.cpp
index 1cad4f02..8c98ae34 100644
--- a/src/components/fs/FS.cpp
+++ b/src/components/fs/FS.cpp
@@ -5,29 +5,28 @@
using namespace Pinetime::Controllers;
-FS::FS(Pinetime::Drivers::SpiNorFlash& driver) :
- flashDriver{ driver },
- lfsConfig{
- .context = this,
- .read = SectorRead,
- .prog = SectorProg,
- .erase = SectorErase,
- .sync = SectorSync,
+FS::FS(Pinetime::Drivers::SpiNorFlash& driver)
+ : flashDriver {driver},
+ lfsConfig {
+ .context = this,
+ .read = SectorRead,
+ .prog = SectorProg,
+ .erase = SectorErase,
+ .sync = SectorSync,
- .read_size = 16,
- .prog_size = 8,
- .block_size = blockSize,
- .block_count = size / blockSize,
- .block_cycles = 1000u,
+ .read_size = 16,
+ .prog_size = 8,
+ .block_size = blockSize,
+ .block_count = size / blockSize,
+ .block_cycles = 1000u,
- .cache_size = 16,
- .lookahead_size = 16,
-
- .name_max = 50,
- .attr_max = 50,
- }
-{ }
+ .cache_size = 16,
+ .lookahead_size = 16,
+ .name_max = 50,
+ .attr_max = 50,
+ } {
+}
void FS::Init() {
@@ -48,7 +47,6 @@ void FS::Init() {
VerifyResource();
LVGLFileSystemInit();
#endif
-
}
void FS::VerifyResource() {
@@ -56,7 +54,7 @@ void FS::VerifyResource() {
resourcesValid = true;
}
-int FS::FileOpen(lfs_file_t* file_p, const char* fileName, const int flags) {
+int FS::FileOpen(lfs_file_t* file_p, const char* fileName, const int flags) {
return lfs_file_open(&lfs, file_p, fileName, flags);
}
@@ -80,27 +78,31 @@ int FS::FileDelete(const char* fileName) {
return lfs_remove(&lfs, fileName);
}
+int FS::DirOpen(const char* path, lfs_dir_t* lfs_dir) {
+ return lfs_dir_open(&lfs, lfs_dir, path);
+}
+
+int FS::DirClose(lfs_dir_t* lfs_dir) {
+ return lfs_dir_close(&lfs, lfs_dir);
+}
+int FS::DirRead(lfs_dir_t* dir, lfs_info* info) {
+ return lfs_dir_read(&lfs, dir, info);
+}
+int FS::DirRewind(lfs_dir_t* dir) {
+ return lfs_dir_rewind(&lfs, dir);
+}
int FS::DirCreate(const char* path) {
return lfs_mkdir(&lfs, path);
}
-
-// Delete directory and all files inside
-int FS::DirDelete(const char* path) {
-
- lfs_dir_t lfs_dir;
- lfs_info entryInfo;
-
- int err;
- err = lfs_dir_open(&lfs, &lfs_dir, path);
- if (err) {
- return err;
- }
- while (lfs_dir_read(&lfs, &lfs_dir, &entryInfo)) {
- lfs_remove(&lfs, entryInfo.name);
- }
- lfs_dir_close(&lfs, &lfs_dir);
- return LFS_ERR_OK;
+int FS::Rename(const char* oldPath, const char* newPath){
+ return lfs_rename(&lfs,oldPath,newPath);
+}
+int FS::Stat(const char* path, lfs_info* info) {
+ return lfs_stat(&lfs, path, info);
+}
+lfs_ssize_t FS::GetFSSize() {
+ return lfs_fs_size(&lfs);
}
/*
@@ -141,17 +143,17 @@ int FS::SectorRead(const struct lfs_config* c, lfs_block_t block, lfs_off_t off,
namespace {
lv_fs_res_t lvglOpen(lv_fs_drv_t* drv, void* file_p, const char* path, lv_fs_mode_t mode) {
-
lfs_file_t* file = static_cast<lfs_file_t*>(file_p);
FS* filesys = static_cast<FS*>(drv->user_data);
- filesys->FileOpen(file, path, LFS_O_RDONLY);
-
- if (file->type == 0) {
- return LV_FS_RES_FS_ERR;
- }
- else {
- return LV_FS_RES_OK;
+ int res = filesys->FileOpen(file, path, LFS_O_RDONLY);
+ if (res == 0) {
+ if (file->type == 0) {
+ return LV_FS_RES_FS_ERR;
+ } else {
+ return LV_FS_RES_OK;
+ }
}
+ return LV_FS_RES_NOT_EX;
}
lv_fs_res_t lvglClose(lv_fs_drv_t* drv, void* file_p) {
@@ -193,5 +195,4 @@ void FS::LVGLFileSystemInit() {
fs_drv.user_data = this;
lv_fs_drv_register(&fs_drv);
-
} \ No newline at end of file
diff --git a/src/components/fs/FS.h b/src/components/fs/FS.h
index 75ba16c8..2b27ae5d 100644
--- a/src/components/fs/FS.h
+++ b/src/components/fs/FS.h
@@ -21,37 +21,49 @@ namespace Pinetime {
int FileDelete(const char* fileName);
+ int DirOpen(const char* path, lfs_dir_t* lfs_dir);
+ int DirClose(lfs_dir_t* lfs_dir);
+ int DirRead(lfs_dir_t* dir, lfs_info* info);
+ int DirRewind(lfs_dir_t* dir);
int DirCreate(const char* path);
- int DirDelete(const char* path);
-
+
+ lfs_ssize_t GetFSSize();
+ int Rename(const char* oldPath, const char* newPath);
+ int Stat(const char* path, lfs_info* info);
void VerifyResource();
- private:
+ static size_t getSize() {
+ return size;
+ }
+ static size_t getBlockSize() {
+ return blockSize;
+ }
+ private:
Pinetime::Drivers::SpiNorFlash& flashDriver;
/*
- * External Flash MAP (4 MBytes)
- *
- * 0x000000 +---------------------------------------+
- * | Bootloader Assets |
- * | 256 KBytes |
- * | |
- * 0x040000 +---------------------------------------+
- * | OTA |
- * | 464 KBytes |
- * | |
- * | |
- * | |
- * 0x0B4000 +---------------------------------------+
- * | File System |
- * | |
- * | |
- * | |
- * | |
- * 0x400000 +---------------------------------------+
- *
- */
+ * External Flash MAP (4 MBytes)
+ *
+ * 0x000000 +---------------------------------------+
+ * | Bootloader Assets |
+ * | 256 KBytes |
+ * | |
+ * 0x040000 +---------------------------------------+
+ * | OTA |
+ * | 464 KBytes |
+ * | |
+ * | |
+ * | |
+ * 0x0B4000 +---------------------------------------+
+ * | File System |
+ * | |
+ * | |
+ * | |
+ * | |
+ * 0x400000 +---------------------------------------+
+ *
+ */
static constexpr size_t startAddress = 0x0B4000;
static constexpr size_t size = 0x34C000;
static constexpr size_t blockSize = 4096;
@@ -65,7 +77,6 @@ namespace Pinetime {
static int SectorErase(const struct lfs_config* c, lfs_block_t block);
static int SectorProg(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, const void* buffer, lfs_size_t size);
static int SectorRead(const struct lfs_config* c, lfs_block_t block, lfs_off_t off, void* buffer, lfs_size_t size);
-
};
}
}
diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp
index ff70363a..2354107b 100644
--- a/src/displayapp/DisplayApp.cpp
+++ b/src/displayapp/DisplayApp.cpp
@@ -214,6 +214,7 @@ void DisplayApp::Refresh() {
} else {
LoadApp(Apps::Alarm, DisplayApp::FullRefreshDirections::None);
}
+ break;
case Messages::ShowPairingKey:
LoadApp(Apps::PassKey, DisplayApp::FullRefreshDirections::Up);
break;
diff --git a/src/displayapp/screens/InfiniPaint.cpp b/src/displayapp/screens/InfiniPaint.cpp
index 93c3c4a0..d279fafc 100644
--- a/src/displayapp/screens/InfiniPaint.cpp
+++ b/src/displayapp/screens/InfiniPaint.cpp
@@ -2,6 +2,8 @@
#include "displayapp/DisplayApp.h"
#include "displayapp/LittleVgl.h"
+#include <algorithm> // std::fill
+
using namespace Pinetime::Applications::Screens;
InfiniPaint::InfiniPaint(Pinetime::Applications::DisplayApp* app,
diff --git a/src/displayapp/screens/WatchFaceDigital.cpp b/src/displayapp/screens/WatchFaceDigital.cpp
index 87695793..4d9eaf37 100644
--- a/src/displayapp/screens/WatchFaceDigital.cpp
+++ b/src/displayapp/screens/WatchFaceDigital.cpp
@@ -44,7 +44,7 @@ WatchFaceDigital::WatchFaceDigital(DisplayApp* app,
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_obj_set_style_local_text_color(bleIcon, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, lv_color_hex(0x0082FC));
lv_label_set_text(bleIcon, Symbols::bluetooth);
lv_obj_align(bleIcon, batteryPlug, LV_ALIGN_OUT_LEFT_MID, -5, 0);
diff --git a/src/systemtask/Messages.h b/src/systemtask/Messages.h
index 516f6462..cc30fdc6 100644
--- a/src/systemtask/Messages.h
+++ b/src/systemtask/Messages.h
@@ -27,6 +27,8 @@ namespace Pinetime {
StopRinging,
MeasureBatteryTimerExpired,
BatteryPercentageUpdated,
+ StartFileTransfer,
+ StopFileTransfer,
};
}
}
diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp
index 28f81243..a95d479d 100644
--- a/src/systemtask/SystemTask.cpp
+++ b/src/systemtask/SystemTask.cpp
@@ -342,6 +342,19 @@ void SystemTask::Work() {
doNotGoToSleep = false;
xTimerStart(dimTimer, 0);
break;
+ case Messages::StartFileTransfer:
+ NRF_LOG_INFO("[systemtask] FS Started");
+ doNotGoToSleep = true;
+ if (isSleeping && !isWakingUp)
+ GoToRunning();
+ //TODO add intent of fs access icon or something
+ break;
+ case Messages::StopFileTransfer:
+ NRF_LOG_INFO("[systemtask] FS Stopped");
+ doNotGoToSleep = false;
+ xTimerStart(dimTimer, 0);
+ //TODO add intent of fs access icon or something
+ break;
case Messages::OnTouchEvent:
if (touchHandler.GetNewTouchInfo()) {
touchHandler.UpdateLvglTouchPoint();