M .github/workflows/main.yml => .github/workflows/main.yml +12 -1
@@ 1,10 1,17 @@
name: CI
-on: [pull_request]
+on:
+ pull_request:
+ types:
+ - opened
+ - synchronize
+ - reopened
+ - ready_for_review
jobs:
check_commit_messages:
name: check commit messages
+ if: github.event.pull_request.draft == false
runs-on: self-hosted
steps:
- name: clone repository
@@ 14,12 21,14 @@ jobs:
- name: check commit messages
run: ./tools/check_commit_messages.py
build:
+ if: github.event.pull_request.draft == false
runs-on: self-hosted
steps:
- name: build
run: echo "Placeholder for old CI scripts"
check_copyright_and_style:
name: check copyright and style
+ if: github.event.pull_request.draft == false
runs-on: self-hosted
steps:
- name: clone repository
@@ 35,6 44,7 @@ jobs:
build_rt1051_binary:
name: build rt1051 binary
+ if: github.event.pull_request.draft == false
needs:
- check_copyright_and_style
runs-on: self-hosted
@@ 56,6 66,7 @@ jobs:
build_linux_binary_and_run_tests:
name: build linux binary and run tests
+ if: github.event.pull_request.draft == false
needs:
- check_copyright_and_style
runs-on: self-hosted
M LICENSE.md => LICENSE.md +37 -2
@@ 1,13 1,48 @@
# Software License Agreement
MuditaOS – https://github.com/mudita/MuditaOS
-Copyright (c) 2017-2020, Mudita Sp. z o.o. All rights reserved.
+Copyright (c) 2017-2021, Mudita Sp. z o.o. All rights reserved.
## Sources of Intellectual Property Included in MuditaOS
Where not otherwise indicated, all MuditaOS content is authored by Mudita engineers and consists of Mudita-owned intellectual property. In some specific instances, MuditaOS will incorporate work done by developers outside of Mudita with their expressed permission.
-## Proprietary code
+### Third party code
+
+The following libraries are included in MuditaOS under specified licenses:
+
+- [Protobuf](https://github.com/protocolbuffers/protobuf/blob/master/LICENSE)
+- [TagLib](https://github.com/mudita/taglib/blob/master/COPYING.MPL)
+- [pugixml](https://github.com/mudita/pugixml/blob/master/LICENSE.md)
+- [lwip](https://github.com/mudita/lwip/blob/master/README)
+- [Libphonenumber](https://github.com/google/libphonenumber/blob/master/LICENSE)
+- [FatFS](https://github.com/abbrev/fatfs)
+- [Littlefs](https://github.com/littlefs-project/littlefs/blob/master/LICENSE.md)
+- [CRC-32](http://csbruce.com/software/)
+- [HowardHinnant - date](https://github.com/HowardHinnant/date/blob/master/LICENSE.txt)
+- [GSL](https://github.com/microsoft/GSL/blob/master/LICENSE)
+- [json11](https://github.com/dropbox/json11/blob/master/LICENSE.txt)
+- [MagicEnumC++](https://github.com/Neargye/magic_enum/blob/master/LICENSE)
+- [microtar](https://github.com/rxi/microtar/blob/master/LICENSE)
+- [RE2](https://github.com/google/re2/blob/master/LICENSE)
+- [SEGGER](https://www.segger.com/products/debug-probes/j-link/technology/about-real-time-transfer/)
+- [tinyexpr](https://github.com/codeplea/tinyexpr/blob/master/LICENSE)
+- [FreeRTOS](https://github.com/aws/amazon-freertos/blob/master/LICENSE)
+- [FreeRTOS Add-ons](https://michaelbecker.github.io/freertos-addons/)
+- [SQLite](https://www.sqlite.org/copyright.html)
+- RT1051 drivers
+- [dr_flac](https://github.com/mackron/dr_libs/)
+- [minimp3](https://github.com/lieff/minimp3/blob/master/LICENSE)
+- [newlib](http://sourceware.org/newlib/)
+- [FreeRTOS + FAT](https://github.com/FreeRTOS/Lab-Project-FreeRTOS-FAT/blob/master/LICENSE.md)
+- [dhcp server](https://github.com/majbthrd/D21ecm/tree/master/dhcp-server)
+
+The following libraries are included only in the development version of MuditaOS under specified licenses:
+
+- [Catch2](https://github.com/catchorg/Catch2/blob/devel/LICENSE.txt)
+- [googletest](https://github.com/google/googletest/blob/master/LICENSE)
+
+### Proprietary code
The following proprietary code is included in MuditaOS:
M changelog.md => changelog.md +15 -0
@@ 1,5 1,18 @@
# MuditaOS changelog
+## Latest Changes
+### Added
+
+* Battery Brownout detection
+
+### Changed
+
+* Input keyboard language files parser from KBD to JSON.
+
+### Fixed
+
+* Fix absent notifications
+
## [0.52.1 2020-12-23]
### Added
@@ 9,6 22,7 @@
* Add custom repeat window for the alarm application.
* Add GUI for custom MMI messages.
* Add CPU frequency shift mechanism to save power.
+* Add oscillator clock switching mechanisms.
### Fixed
@@ 16,6 30,7 @@
* Fix missing texts in the desktop application.
* Fix occasional cases of no audio during an incoming call.
* Charging state icons in simulator
+* Fix Debug UART conditional enabling
### Changed
M config/ProjectConfig.cmake => config/ProjectConfig.cmake +8 -0
@@ 19,6 19,13 @@ else()
set (USBCDC_ECHO_ENABLED 0 CACHE INTERNAL "")
endif()
+#add Debug LUART enable option
+if (${LOG_REDIRECT} STREQUAL "RTT_LUART")
+ set (LOG_LUART_ENABLED 1 CACHE INTERNAL "")
+else()
+ set (LOG_LUART_ENABLED 0 CACHE INTERNAL "")
+endif()
+
#Config options described in README.md
set(PROJECT_CONFIG_DEFINITIONS
@@ 26,6 33,7 @@ set(PROJECT_CONFIG_DEFINITIONS
LOG_REDIRECT=${LOG_REDIRECT}
SYSTEM_VIEW_ENABLED=${SYSTEM_VIEW_ENABLED}
USBCDC_ECHO_ENABLED=${USBCDC_ECHO_ENABLED}
+ LOG_LUART_ENABLED=${LOG_LUART_ENABLED}
PROJECT_CONFIG_USER_DYNMEM_SIZE=9*1024*1024
CACHE INTERNAL ""
)
M => +1 -1
@@ 17,7 17,7 @@ REPO_ROOT="${SCRIPT_DIR%/*}"
pushd ${REPO_ROOT} >> /dev/null
LICENSE1="Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved."
LICENSE1="Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved."
LICENSE2="For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md"
CHECK_ONLY="false"
M doc/development_workflow.md => doc/development_workflow.md +2 -1
@@ 53,7 53,8 @@ Before submitting a Pull Request please go through some basic checks:
- test your changes on both Linux and RT1051 platforms (please pay special attention to the things you might break unintentionally, e.g. when working on calling funcionality, check call log too)
- [include changelog description](changelog_howto.md) (if applicable),
- run unit tests (`make check`),
-- check if your code formatting complies with [`.clang-format`](../clang-format).
+- check if your code formatting complies with [`.clang-format`](../clang-format),
+- whenever you add a third party software to MuditaOS source code, please make sure that the software component is added to a dedicated [section in `LICENSE.md` file on MuditaOS GitHub repository](../LICENSE.md) with a link to the text of the license where applicable.
As a part of our continuous integration proccess we will be soon introducing our own [test harness](../test/README.md).
M doc/i18n.md => doc/i18n.md +36 -21
@@ 26,35 26,50 @@ The keys on the left side refer to the values on the right side. These values ar
The first four values of JSON language files tell MuditaOS, which keyboard input language is the specific language setting using:
```c++
-"common_kbd_lower": "lang_eng_lower",
-"common_kbd_upper": "lang_eng_upper",
+"common_kbd_lower": "English_lower",
+"common_kbd_upper": "English_upper",
"common_kbd_numeric": "numeric",
"common_kbd_phone": "phone",
(...)
```
-These values are names of KPROF files, which are located in [the image/assets/profiles folder](../image/assets/profiles/).
+These values are names of JSON files, which are located in [the image/assets/profiles folder](../image/assets/profiles/).
-Every language has its own files for upper and lower letters. Here's an example of a working implementation of buttons 2 and 3 in the `lang_eng_lower` file:
+Every language has its own files for upper and lower letters. Here's an example of a working JSON file for `English_lower.json`:
```c++
-(...)
-#NumericKey2 = 32,
-32
-true
-'a','b','c'
-0,0,0
-#NumericKey3 = 33,
-33
-true
-'d','e','f'
-0,0,0
-(...)
+{
+ "filetype": "normal",
+ "31": ".,_:;)(?!#/*+",
+ "32": "abc",
+ "33": "def",
+ "41": "ghi",
+ "42": "jkl",
+ "43": "mno",
+ "51": "pqrs",
+ "52": "tuv",
+ "53": "wxyz",
+ "61": "0x0A",
+ "62": " ",
+ "63": "0x08"
+}
```
-Values explanation:
-- The first value is the number of the button (31 means button "1", 32 means button "2" etc.)
-- The second value is true if the key is cyclic or false otherwise
-- The third line is a list of values that would be available by clicking this button
-- The last line includes zeros written as many times as there are characters available under the button
+The first value declares the type of this file:
+- `normal` - they are shown in input language settings, user can change it through GUI (e.g. from English to Polish).
+- `special` - they won't show in input language settings, they can be modified only in code (e.g. numeric keyboard).
+
+Normal-type files will be displayed in settings by their filename (e.g. English_lower will be shown as English). When you add a new input language you should always include files for lower and upper letters for it.
+
+Next key-value pairs includes code of the key (key) and characters available under this key (value).
+
+Files naming pattern should be: `<language>_<lower/upper>`, eg. correct implementation of Rodian input language should consist of two files: `Rodian_lower.json` and `Rodian_upper.json`.
+
+We also distinguish two types of button presses:
+- `shortpress` - they are used to write letters taken from loaded input language file.
+- `longpress` - this type of button press is used, when user keeps button pressed for at least 2 seconds and then releases. In text it is used to write numbers.
+
+Example: If you are using English keyboard language for lower letters and press `1` button (keycode `31`) and quickly release it, then in SMS textbox `a` letter will appear. But if you hold this button pressed for at least 2 seconds and then release it, `1` number will appear.
+
+Definition for every key code used in the phone is in [the key_codes.hpp file (KeyCodes enum)](../module-bsp/bsp/keyboard/key_codes.hpp)
### Date and time
M image/assets/lang/Deutsch.json => image/assets/lang/Deutsch.json +2 -2
@@ 1,6 1,6 @@
{
- "common_kbd_lower": "lang_de_lower",
- "common_kbd_upper": "lang_de_upper",
+ "common_kbd_lower": "Deutsch_lower",
+ "common_kbd_upper": "Deutsch_upper",
"common_kbd_numeric": "numeric",
"common_add": "ADD",
"common_open": "OPEN",
M image/assets/lang/English.json => image/assets/lang/English.json +2 -3
@@ 1,9 1,8 @@
{
- "common_kbd_lower": "lang_eng_lower",
- "common_kbd_upper": "lang_eng_upper",
+ "common_kbd_lower": "English_lower",
+ "common_kbd_upper": "English_upper",
"common_kbd_numeric": "numeric",
"common_kbd_phone": "phone",
-
"common_add": "ADD",
"common_open": "OPEN",
"common_call": "CALL",
M image/assets/lang/Espanol.json => image/assets/lang/Espanol.json +2 -2
@@ 1,6 1,6 @@
{
- "common_kbd_lower": "lang_sp_lower",
- "common_kbd_upper": "lang_sp_upper",
+ "common_kbd_lower": "Espanol_lower",
+ "common_kbd_upper": "Espanol_upper",
"common_kbd_numeric": "numeric",
"common_add": "ADD",
"common_open": "ABIERTA",
M image/assets/lang/Francais.json => image/assets/lang/Francais.json +2 -2
@@ 1,6 1,6 @@
{
- "common_kbd_lower": "lang_fr_lower",
- "common_kbd_upper": "lang_fr_upper",
+ "common_kbd_lower": "Francais_lower",
+ "common_kbd_upper": "Francais_upper",
"common_kbd_numeric": "numeric",
"common_add": "ADD",
"common_open": "OPEN",
M image/assets/lang/Polski.json => image/assets/lang/Polski.json +2 -2
@@ 1,6 1,6 @@
{
- "common_kbd_lower": "lang_pl_lower",
- "common_kbd_upper": "lang_pl_upper",
+ "common_kbd_lower": "Polski_lower",
+ "common_kbd_upper": "Polski_upper",
"common_kbd_numeric": "numeric",
"common_add": "DODAJ",
"common_open": "OTWÓRZ",
A image/assets/profiles/Deutsch_lower.json => image/assets/profiles/Deutsch_lower.json +15 -0
@@ 0,0 1,15 @@
+{
+ "filetype": "normal",
+ "31": ".,_:;)(?!#/*+",
+ "32": "abcä",
+ "33": "def",
+ "41": "ghi",
+ "42": "jkl",
+ "43": "mnoö",
+ "51": "pqrsß",
+ "52": "tuvü",
+ "53": "wxyz",
+ "61": "0x0A",
+ "62": " ",
+ "63": "0x08"
+}
A image/assets/profiles/Deutsch_upper.json => image/assets/profiles/Deutsch_upper.json +15 -0
@@ 0,0 1,15 @@
+{
+ "filetype": "normal",
+ "31": ".,_:;)(?!#/*+",
+ "32": "ABCÄ",
+ "33": "DEF",
+ "41": "GHI",
+ "42": "JKL",
+ "43": "MNOÖ",
+ "51": "PQRSß",
+ "52": "TUVÜ",
+ "53": "WXYZ",
+ "61": "0x0A",
+ "62": " ",
+ "63": "0x08"
+}
A image/assets/profiles/English_lower.json => image/assets/profiles/English_lower.json +15 -0
@@ 0,0 1,15 @@
+{
+ "filetype": "normal",
+ "31": ".,_:;)(?!#/*+",
+ "32": "abc",
+ "33": "def",
+ "41": "ghi",
+ "42": "jkl",
+ "43": "mno",
+ "51": "pqrs",
+ "52": "tuv",
+ "53": "wxyz",
+ "61": "0x0A",
+ "62": " ",
+ "63": "0x08"
+}
A image/assets/profiles/English_upper.json => image/assets/profiles/English_upper.json +15 -0
@@ 0,0 1,15 @@
+{
+ "filetype": "normal",
+ "31": ".,_:;)(?!#/*+",
+ "32": "ABC",
+ "33": "DEF",
+ "41": "GHI",
+ "42": "JKL",
+ "43": "MNO",
+ "51": "PQRS",
+ "52": "TUV",
+ "53": "WXYZ",
+ "61": "0x0A",
+ "62": " ",
+ "63": "0x08"
+}
A image/assets/profiles/Espanol_lower.json => image/assets/profiles/Espanol_lower.json +15 -0
@@ 0,0 1,15 @@
+{
+ "filetype": "normal",
+ "31": ".,_:;)(?!#/*+",
+ "32": "abcá",
+ "33": "defé",
+ "41": "ghií",
+ "42": "jkl",
+ "43": "mnoóñ",
+ "51": "pqrs",
+ "52": "tuvúü",
+ "53": "wxyz",
+ "61": "0x0A",
+ "62": " ",
+ "63": "0x08"
+}
A image/assets/profiles/Espanol_upper.json => image/assets/profiles/Espanol_upper.json +15 -0
@@ 0,0 1,15 @@
+{
+ "filetype": "normal",
+ "31": ".,_:;)(?!#/*+",
+ "32": "ABCÁ",
+ "33": "DEFÉ",
+ "41": "GHIÍ",
+ "42": "JKL",
+ "43": "MNOÓÑ",
+ "51": "PQRS",
+ "52": "TUVÚÜ",
+ "53": "WXYZ",
+ "61": "0x0A",
+ "62": " ",
+ "63": "0x08"
+}
A image/assets/profiles/Francais_lower.json => image/assets/profiles/Francais_lower.json +15 -0
@@ 0,0 1,15 @@
+{
+ "filetype": "normal",
+ "31": ".,_:;)(?!#/*+",
+ "32": "abcàâç",
+ "33": "deféèêë",
+ "41": "ghiîï",
+ "42": "jkl",
+ "43": "mnoô",
+ "51": "pqrs",
+ "52": "tuvûùü",
+ "53": "wxyzÿ",
+ "61": "0x0A",
+ "62": " ",
+ "63": "0x08"
+}
A image/assets/profiles/Francais_upper.json => image/assets/profiles/Francais_upper.json +15 -0
@@ 0,0 1,15 @@
+{
+ "filetype": "normal",
+ "31": ".,_:;)(?!#/*+",
+ "32": "ABCÀÂÇ",
+ "33": "DEFÉÈÊË",
+ "41": "GHIÎÏ",
+ "42": "JKL",
+ "43": "MNOÔ",
+ "51": "PQRS",
+ "52": "TUVÛÙÜ",
+ "53": "WXYZŸ",
+ "61": "0x0A",
+ "62": " ",
+ "63": "0x08"
+}
A image/assets/profiles/Polski_lower.json => image/assets/profiles/Polski_lower.json +15 -0
@@ 0,0 1,15 @@
+{
+ "filetype": "normal",
+ "31": ".,_:;)(?!#/*+",
+ "32": "abcąć",
+ "33": "defę",
+ "41": "ghi",
+ "42": "jklł",
+ "43": "mnońó",
+ "51": "pqrsś",
+ "52": "tuv",
+ "53": "wxyzźż",
+ "61": "0x0A",
+ "62": " ",
+ "63": "0x08"
+}
A image/assets/profiles/Polski_upper.json => image/assets/profiles/Polski_upper.json +15 -0
@@ 0,0 1,15 @@
+{
+ "filetype": "normal",
+ "31": ".,_:;)(?!#/*+",
+ "32": "ABCĄĆ",
+ "33": "DEFĘ",
+ "41": "GHI",
+ "42": "JKLŁ",
+ "43": "MNOŃÓ",
+ "51": "PQRSŚ",
+ "52": "TUV",
+ "53": "WXYZŹŻ",
+ "61": "0x0A",
+ "62": " ",
+ "63": "0x08"
+}
D image/assets/profiles/lang_de_lower.kprof => image/assets/profiles/lang_de_lower.kprof +0 -99
@@ 1,99 0,0 @@
-#name of the profile
-lang_de_lower
-#NumericKey1 = 31,
-31
-true
-'.',',','_',':',';',')','(','?','!','#','/','*','+'
-0,0,0,0,0,0,0,0,0,0,0,0,0
-#NumericKey2 = 32,
-32
-true
-'a','b','c','ä'
-0,0,0,0
-#NumericKey3 = 33,
-33
-true
-'d','e','f'
-0,0,0
-#NumericKey4 = 41,
-41
-true
-'g','h','i'
-0,0,0
-#NumericKey5 = 42,
-42
-true
-'j','k','l'
-0,0,0
-#NumericKey6 = 43,
-43
-true
-'m','n','o','ö'
-0,0,0,0
-#NumericKey7 = 51,
-51
-true
-'p','q','r','s','ß'
-0,0,0,0,0
-#NumericKey8 = 52,
-52
-true
-'t','u','v','ü'
-0,0,0,0
-#NumericKey9 = 53,
-53
-true
-'w','x','y','z'
-0,0,0,0
-#NumericKey0 = 62,
-62
-false
-' '
-0
-#NumericKeyAst = 61,
-61
-false
-0x08
-0
-#NumericKeyPnd = 63,
-63
-false
-0x0A
-0
-#JoystickLeft = 11,
-11
-false
-''
-0
-#JoystickRight = 13,
-13
-false
-''
-0
-#JoystickUp = 2,
-2
-false
-''
-0
-#JoystickDown = 22,
-22
-false
-''
-0
-#JoystickEnter = 12,
-12
-false
-''
-0
-#FnLeft = 21,//1,
-#FnRight = 23,//3,
-23
-false
-''
-2000
-#VolUp = 4,
-#VolDown = 14,
-#Torch = 24,
-#SSwitchUp = 34,
-#SSwitchDown = 54,
-#SSwitchMid = 44
D image/assets/profiles/lang_de_upper.kprof => image/assets/profiles/lang_de_upper.kprof +0 -99
@@ 1,99 0,0 @@
-#name of the profile
-lang_de_upper
-#NumericKey1 = 31,
-31
-true
-'.',',','_',':',';',')','(','?','!','#','/','*','+'
-0,0,0,0,0,0,0,0,0,0,0,0,0
-#NumericKey2 = 32,
-32
-true
-'A','B','C','Ä'
-0,0,0,0
-#NumericKey3 = 33,
-33
-true
-'D','E','F'
-0,0,0
-#NumericKey4 = 41,
-41
-true
-'G','H','I'
-0,0,0
-#NumericKey5 = 42,
-42
-true
-'J','K','L'
-0,0,0
-#NumericKey6 = 43,
-43
-true
-'M','N','O','Ö'
-0,0,0,0
-#NumericKey7 = 51,
-51
-true
-'P','Q','R','S','ß'
-0,0,0,0,0
-#NumericKey8 = 52,
-52
-true
-'T','U','V','Ü'
-0,0,0,0
-#NumericKey9 = 53,
-53
-true
-'W','X','Y','Z'
-0,0,0,0
-#NumericKey0 = 62,
-62
-false
-' '
-0
-#NumericKeyAst = 61,
-61
-false
-0x08
-0
-#NumericKeyPnd = 63,
-63
-false
-0x0A
-0
-#JoystickLeft = 11,
-11
-false
-''
-0
-#JoystickRight = 13,
-13
-false
-''
-0
-#JoystickUp = 2,
-2
-false
-''
-0
-#JoystickDown = 22,
-22
-false
-''
-0
-#JoystickEnter = 12,
-12
-false
-''
-0
-#FnLeft = 21,//1,
-#FnRight = 23,//3,
-23
-false
-''
-2000
-#VolUp = 4,
-#VolDown = 14,
-#Torch = 24,
-#SSwitchUp = 34,
-#SSwitchDown = 54,
-#SSwitchMid = 44
D image/assets/profiles/lang_eng_lower.kprof => image/assets/profiles/lang_eng_lower.kprof +0 -99
@@ 1,99 0,0 @@
-#name of the profile
-lang_eng_lower
-#NumericKey1 = 31,
-31
-true
-'.',',','_',':',';',')','(','?','!','#','/','*','+'
-0,0,0,0,0,0,0,0,0,0,0,0,0
-#NumericKey2 = 32,
-32
-true
-'a','b','c'
-0,0,0
-#NumericKey3 = 33,
-33
-true
-'d','e','f'
-0,0,0
-#NumericKey4 = 41,
-41
-true
-'g','h','i'
-0,0,0
-#NumericKey5 = 42,
-42
-true
-'j','k','l'
-0,0,0
-#NumericKey6 = 43,
-43
-true
-'m','n','o'
-0,0,0
-#NumericKey7 = 51,
-51
-true
-'p','q','r','s'
-0,0,0,0
-#NumericKey8 = 52,
-52
-true
-'t','u','v'
-0,0,0
-#NumericKey9 = 53,
-53
-true
-'w','x','y','z'
-0,0,0,0
-#NumericKey0 = 62,
-62
-false
-' '
-0
-#NumericKeyAst = 61,
-61
-false
-0x0A
-0
-#NumericKeyPnd = 63,
-63
-false
-0x08
-0
-#JoystickLeft = 11,
-11
-false
-''
-0
-#JoystickRight = 13,
-13
-false
-''
-0
-#JoystickUp = 2,
-2
-false
-''
-0
-#JoystickDown = 22,
-22
-false
-''
-0
-#JoystickEnter = 12,
-12
-false
-''
-0
-#FnLeft = 21,//1,
-#FnRight = 23,//3,
-23
-false
-''
-2000
-#VolUp = 4,
-#VolDown = 14,
-#Torch = 24,
-#SSwitchUp = 34,
-#SSwitchDown = 54,
-#SSwitchMid = 44
D image/assets/profiles/lang_eng_upper.kprof => image/assets/profiles/lang_eng_upper.kprof +0 -99
@@ 1,99 0,0 @@
-#name of the profile
-lang_eng_upper
-#NumericKey1 = 31,
-31
-true
-'.',',','_',':',';',')','(','?','!','#','/','*','+'
-0,0,0,0,0,0,0,0,0,0,0,0,0
-#NumericKey2 = 32,
-32
-true
-'A','B','C'
-0,0,0
-#NumericKey3 = 33,
-33
-true
-'D','E','F'
-0,0,0
-#NumericKey4 = 41,
-41
-true
-'G','H','I'
-0,0,0
-#NumericKey5 = 42,
-42
-true
-'J','K','L'
-0,0,0
-#NumericKey6 = 43,
-43
-true
-'M','N','O'
-0,0,0
-#NumericKey7 = 51,
-51
-true
-'P','Q','R','S'
-0,0,0,0
-#NumericKey8 = 52,
-52
-true
-'T','U','V'
-0,0,0
-#NumericKey9 = 53,
-53
-true
-'W','X','Y','Z'
-0,0,0,0
-#NumericKey0 = 62,
-62
-false
-' '
-0
-#NumericKeyAst = 61,
-61
-false
-0x0A
-0
-#NumericKeyPnd = 63,
-63
-false
-0x08
-0
-#JoystickLeft = 11,
-11
-false
-''
-0
-#JoystickRight = 13,
-13
-false
-''
-0
-#JoystickUp = 2,
-2
-false
-''
-0
-#JoystickDown = 22,
-22
-false
-''
-0
-#JoystickEnter = 12,
-12
-false
-''
-0
-#FnLeft = 21,//1,
-#FnRight = 23,//3,
-23
-false
-''
-2000
-#VolUp = 4,
-#VolDown = 14,
-#Torch = 24,
-#SSwitchUp = 34,
-#SSwitchDown = 54,
-#SSwitchMid = 44
D image/assets/profiles/lang_fr_lower.kprof => image/assets/profiles/lang_fr_lower.kprof +0 -99
@@ 1,99 0,0 @@
-#name of the profile
-lang_fr_lower
-#NumericKey1 = 31,
-31
-true
-'.',',','_',':',';',')','(','?','!','#','/','*','+'
-0,0,0,0,0,0,0,0,0,0,0,0,0
-#NumericKey2 = 32,
-32
-true
-'a','b','c','à','â','ç'
-0,0,0,0,0,0
-#NumericKey3 = 33,
-33
-true
-'d','e','f','é','è','ê','ë'
-0,0,0,0,0,0,0
-#NumericKey4 = 41,
-41
-true
-'g','h','i','î','ï'
-0,0,0,0,0
-#NumericKey5 = 42,
-42
-true
-'j','k','l'
-0,0,0
-#NumericKey6 = 43,
-43
-true
-'m','n','o','ô'
-0,0,0,0
-#NumericKey7 = 51,
-51
-true
-'p','q','r','s'
-0,0,0,0
-#NumericKey8 = 52,
-52
-true
-'t','u','v','û','ù','ü'
-0,0,0,0,0,0
-#NumericKey9 = 53,
-53
-true
-'w','x','y','z','ÿ'
-0,0,0,0,0
-#NumericKey0 = 62,
-62
-false
-' '
-0
-#NumericKeyAst = 61,
-61
-false
-0x0A
-0
-#NumericKeyPnd = 63,
-63
-false
-0x08
-0
-#JoystickLeft = 11,
-11
-false
-''
-0
-#JoystickRight = 13,
-13
-false
-''
-0
-#JoystickUp = 2,
-2
-false
-''
-0
-#JoystickDown = 22,
-22
-false
-''
-0
-#JoystickEnter = 12,
-12
-false
-''
-0
-#FnLeft = 21,//1,
-#FnRight = 23,//3,
-23
-false
-''
-2000
-#VolUp = 4,
-#VolDown = 14,
-#Torch = 24,
-#SSwitchUp = 34,
-#SSwitchDown = 54,
-#SSwitchMid = 44
D image/assets/profiles/lang_fr_upper.kprof => image/assets/profiles/lang_fr_upper.kprof +0 -99
@@ 1,99 0,0 @@
-#name of the profile
-lang_fr_upper
-#NumericKey1 = 31,
-31
-true
-'.',',','_',':',';',')','(','?','!','#','/','*','+'
-0,0,0,0,0,0,0,0,0,0,0,0,0
-#NumericKey2 = 32,
-32
-true
-'A','B','C','À','Â','Ç'
-0,0,0,0,0,0
-#NumericKey3 = 33,
-33
-true
-'D','E','F','É','È','Ê','Ë'
-0,0,0,0,0,0,0
-#NumericKey4 = 41,
-41
-true
-'G','H','I','Î','Ï'
-0,0,0,0,0
-#NumericKey5 = 42,
-42
-true
-'J','K','L'
-0,0,0
-#NumericKey6 = 43,
-43
-true
-'M','N','O','Ô'
-0,0,0,0
-#NumericKey7 = 51,
-51
-true
-'P','Q','R','S'
-0,0,0,0
-#NumericKey8 = 52,
-52
-true
-'T','U','V','Û','Ù','Ü'
-0,0,0,0,0,0
-#NumericKey9 = 53,
-53
-true
-'W','X','Y','Z','Ÿ'
-0,0,0,0,0
-#NumericKey0 = 62,
-62
-false
-' '
-0
-#NumericKeyAst = 61,
-61
-false
-0x0A
-0
-#NumericKeyPnd = 63,
-63
-false
-0x08
-0
-#JoystickLeft = 11,
-11
-false
-''
-0
-#JoystickRight = 13,
-13
-false
-''
-0
-#JoystickUp = 2,
-2
-false
-''
-0
-#JoystickDown = 22,
-22
-false
-''
-0
-#JoystickEnter = 12,
-12
-false
-''
-0
-#FnLeft = 21,//1,
-#FnRight = 23,//3,
-23
-false
-''
-2000
-#VolUp = 4,
-#VolDown = 14,
-#Torch = 24,
-#SSwitchUp = 34,
-#SSwitchDown = 54,
-#SSwitchMid = 44
D image/assets/profiles/lang_pl_lower.kprof => image/assets/profiles/lang_pl_lower.kprof +0 -99
@@ 1,99 0,0 @@
-#name of the profile
-lang_pl_lower
-#NumericKey1 = 31,
-31
-true
-'.',',','_',':',';',')','(','?','!','#','/','*','+'
-0,0,0,0,0,0,0,0,0,0,0,0,0
-#NumericKey2 = 32,
-32
-true
-'a','b','c','ą','ć'
-0,0,0,0,0
-#NumericKey3 = 33,
-33
-true
-'d','e','f','ę'
-0,0,0,0
-#NumericKey4 = 41,
-41
-true
-'g','h','i'
-0,0,0
-#NumericKey5 = 42,
-42
-true
-'j','k','l','ł'
-0,0,0,0
-#NumericKey6 = 43,
-43
-true
-'m','n','o','ń','ó'
-0,0,0,0,0
-#NumericKey7 = 51,
-51
-true
-'p','q','r','s','ś'
-0,0,0,0,0
-#NumericKey8 = 52,
-52
-true
-'t','u','v'
-0,0,0
-#NumericKey9 = 53,
-53
-true
-'w','x','y','z','ź','ż'
-0,0,0,0,0,0
-#NumericKey0 = 62,
-62
-false
-' '
-0
-#NumericKeyAst = 61,
-61
-false
-0x08
-0
-#NumericKeyPnd = 63,
-63
-false
-0x0A
-0
-#JoystickLeft = 11,
-11
-false
-''
-0
-#JoystickRight = 13,
-13
-false
-''
-0
-#JoystickUp = 2,
-2
-false
-''
-0
-#JoystickDown = 22,
-22
-false
-''
-0
-#JoystickEnter = 12,
-12
-false
-''
-0
-#FnLeft = 21,//1,
-#FnRight = 23,//3,
-23
-false
-''
-2000
-#VolUp = 4,
-#VolDown = 14,
-#Torch = 24,
-#SSwitchUp = 34,
-#SSwitchDown = 54,
-#SSwitchMid = 44
D image/assets/profiles/lang_pl_upper.kprof => image/assets/profiles/lang_pl_upper.kprof +0 -99
@@ 1,99 0,0 @@
-#name of the profile
-lang_pl_upper
-#NumericKey1 = 31,
-31
-true
-'.',',','_',':',';',')','(','?','!','#','/','*','+'
-0,0,0,0,0,0,0,0,0,0,0,0,0
-#NumericKey2 = 32,
-32
-true
-'A','B','C','Ą','Ć'
-0,0,0,0,0
-#NumericKey3 = 33,
-33
-true
-'D','E','F','Ę'
-0,0,0,0
-#NumericKey4 = 41,
-41
-true
-'G','H','I'
-0,0,0
-#NumericKey5 = 42,
-42
-true
-'J','K','L','Ł'
-0,0,0,0
-#NumericKey6 = 43,
-43
-true
-'M','N','O','Ń','Ó'
-0,0,0,0,0
-#NumericKey7 = 51,
-51
-true
-'P','Q','R','S','Ś'
-0,0,0,0,0
-#NumericKey8 = 52,
-52
-true
-'T','U','V'
-0,0,0
-#NumericKey9 = 53,
-53
-true
-'W','X','Y','Z','Ź','Ż'
-0,0,0,0,0,0
-#NumericKey0 = 62,
-62
-false
-' '
-0
-#NumericKeyAst = 61,
-61
-false
-0x08
-0
-#NumericKeyPnd = 63,
-63
-false
-0x0A
-0
-#JoystickLeft = 11,
-11
-false
-''
-0
-#JoystickRight = 13,
-13
-false
-''
-0
-#JoystickUp = 2,
-2
-false
-''
-0
-#JoystickDown = 22,
-22
-false
-''
-0
-#JoystickEnter = 12,
-12
-false
-''
-0
-#FnLeft = 21,//1,
-#FnRight = 23,//3,
-23
-false
-''
-2000
-#VolUp = 4,
-#VolDown = 14,
-#Torch = 24,
-#SSwitchUp = 34,
-#SSwitchDown = 54,
-#SSwitchMid = 44
D image/assets/profiles/lang_sp_lower.kprof => image/assets/profiles/lang_sp_lower.kprof +0 -99
@@ 1,99 0,0 @@
-#name of the profile
-lang_sp_lower
-#NumericKey1 = 31,
-31
-true
-'.',',','_',':',';',')','(','?','!','#','/','*','+'
-0,0,0,0,0,0,0,0,0,0,0,0,0
-#NumericKey2 = 32,
-32
-true
-'a','b','c','á'
-0,0,0,0
-#NumericKey3 = 33,
-33
-true
-'d','e','f','é'
-0,0,0,0
-#NumericKey4 = 41,
-41
-true
-'g','h','i','í'
-0,0,0,0
-#NumericKey5 = 42,
-42
-true
-'j','k','l'
-0,0,0
-#NumericKey6 = 43,
-43
-true
-'m','n','o','ó','ñ'
-0,0,0,0,0
-#NumericKey7 = 51,
-51
-true
-'p','q','r','s'
-0,0,0,0
-#NumericKey8 = 52,
-52
-true
-'t','u','v','ú','ü'
-0,0,0,0,0
-#NumericKey9 = 53,
-53
-true
-'w','x','y','z'
-0,0,0,0
-#NumericKey0 = 62,
-62
-false
-' '
-0
-#NumericKeyAst = 61,
-61
-false
-0x08
-0
-#NumericKeyPnd = 63,
-63
-false
-0x0A
-0
-#JoystickLeft = 11,
-11
-false
-''
-0
-#JoystickRight = 13,
-13
-false
-''
-0
-#JoystickUp = 2,
-2
-false
-''
-0
-#JoystickDown = 22,
-22
-false
-''
-0
-#JoystickEnter = 12,
-12
-false
-''
-0
-#FnLeft = 21,//1,
-#FnRight = 23,//3,
-23
-false
-''
-2000
-#VolUp = 4,
-#VolDown = 14,
-#Torch = 24,
-#SSwitchUp = 34,
-#SSwitchDown = 54,
-#SSwitchMid = 44
D image/assets/profiles/lang_sp_upper.kprof => image/assets/profiles/lang_sp_upper.kprof +0 -99
@@ 1,99 0,0 @@
-#name of the profile
-lang_sp_upper
-#NumericKey1 = 31,
-31
-true
-'.',',','_',':',';',')','(','?','!','#','/','*','+'
-0,0,0,0,0,0,0,0,0,0,0,0,0
-#NumericKey2 = 32,
-32
-true
-'A','B','C','Á'
-0,0,0,0
-#NumericKey3 = 33,
-33
-true
-'D','E','F','É'
-0,0,0,0
-#NumericKey4 = 41,
-41
-true
-'G','H','I','Í'
-0,0,0,0
-#NumericKey5 = 42,
-42
-true
-'J','K','L'
-0,0,0
-#NumericKey6 = 43,
-43
-true
-'M','N','O','Ó','Ñ'
-0,0,0,0,0
-#NumericKey7 = 51,
-51
-true
-'P','Q','R','S'
-0,0,0,0
-#NumericKey8 = 52,
-52
-true
-'T','U','V','Ú','Ü'
-0,0,0,0,0
-#NumericKey9 = 53,
-53
-true
-'W','X','Y','Z'
-0,0,0,0
-#NumericKey0 = 62,
-62
-false
-' '
-0
-#NumericKeyAst = 61,
-61
-false
-0x08
-0
-#NumericKeyPnd = 63,
-63
-false
-0x0A
-0
-#JoystickLeft = 11,
-11
-false
-''
-0
-#JoystickRight = 13,
-13
-false
-''
-0
-#JoystickUp = 2,
-2
-false
-''
-0
-#JoystickDown = 22,
-22
-false
-''
-0
-#JoystickEnter = 12,
-12
-false
-''
-0
-#FnLeft = 21,//1,
-#FnRight = 23,//3,
-23
-false
-''
-2000
-#VolUp = 4,
-#VolDown = 14,
-#Torch = 24,
-#SSwitchUp = 34,
-#SSwitchDown = 54,
-#SSwitchMid = 44
A image/assets/profiles/numeric.json => image/assets/profiles/numeric.json +21 -0
@@ 0,0 1,21 @@
+{
+ "filetype": "special",
+ "31": "1",
+ "32": "2",
+ "33": "3",
+ "41": "4",
+ "42": "5",
+ "43": "6",
+ "51": "7",
+ "52": "8",
+ "53": "9",
+ "61": "0x08",
+ "62": "0",
+ "63": "*#",
+ "11": "",
+ "13": "",
+ "2": "",
+ "22": "",
+ "12": "",
+ "23": ""
+}
D image/assets/profiles/numeric.kprof => image/assets/profiles/numeric.kprof +0 -99
@@ 1,99 0,0 @@
-#name of the profile
-numeric
-#NumericKey1 = 31,
-31
-false
-'1'
-0
-#NumericKey2 = 32,
-32
-false
-'2'
-0
-#NumericKey3 = 33,
-33
-false
-'3'
-0
-#NumericKey4 = 41,
-41
-false
-'4'
-0
-#NumericKey5 = 42,
-42
-false
-'5'
-0
-#NumericKey6 = 43,
-43
-false
-'6'
-0
-#NumericKey7 = 51,
-51
-false
-'7'
-0
-#NumericKey8 = 52,
-52
-false
-'8'
-0
-#NumericKey9 = 53,
-53
-false
-'9'
-0
-#NumericKey0 = 62,
-62
-false
-'0'
-0
-#NumericKeyAst = 61,
-61
-false
-0x08
-0
-#NumericKeyPnd = 63,
-63
-false
-'*','#'
-0,0
-#JoystickLeft = 11,
-11
-false
-''
-0
-#JoystickRight = 13,
-13
-false
-''
-0
-#JoystickUp = 2,
-2
-false
-''
-0
-#JoystickDown = 22,
-22
-false
-''
-0
-#JoystickEnter = 12,
-12
-false
-''
-0
-#FnLeft = 21,//1,
-#FnRight = 23,//3,
-23
-false
-''
-3000
-#VolUp = 4,
-#VolDown = 14,
-#Torch = 24,
-#SSwitchUp = 34,
-#SSwitchDown = 54,
-#SSwitchMid = 44
A image/assets/profiles/phone.json => image/assets/profiles/phone.json +22 -0
@@ 0,0 1,22 @@
+{
+ "filetype": "special",
+ "31": "1",
+ "32": "2",
+ "33": "3",
+ "41": "4",
+ "42": "5",
+ "43": "6",
+ "51": "7",
+ "52": "8",
+ "53": "9",
+ "61": "*",
+ "62": "0",
+ "63": "#",
+ "11": "",
+ "13": "",
+ "2": "",
+ "22": "",
+ "12": "",
+ "23": ""
+}
+
D image/assets/profiles/phone.kprof => image/assets/profiles/phone.kprof +0 -103
@@ 1,103 0,0 @@
-#name of the profile
-phone
-#NumericKey1 = 31,
-31
-false
-'1'
-0
-#NumericKey2 = 32,
-32
-false
-'2'
-0
-#NumericKey3 = 33,
-33
-false
-'3'
-0
-#NumericKey4 = 41,
-41
-false
-'4'
-0
-#NumericKey5 = 42,
-42
-false
-'5'
-0
-#NumericKey6 = 43,
-43
-false
-'6'
-0
-#NumericKey7 = 51,
-51
-false
-'7'
-0
-#NumericKey8 = 52,
-52
-false
-'8'
-0
-#NumericKey9 = 53,
-53
-false
-'9'
-0
-#NumericKey0 = 62,
-62
-false
-'0'
-0
-#NumericKeyAst = 61,
-61
-false
-'*'
-0
-#NumericKeyPnd = 63,
-63
-false
-'#'
-3000
-#JoystickLeft = 11,
-11
-false
-''
-0
-#JoystickRight = 13,
-13
-false
-''
-0
-#JoystickUp = 2,
-2
-false
-''
-0
-#JoystickDown = 22,
-22
-false
-''
-0
-#JoystickEnter = 12,
-12
-false
-''
-0
-#FnLeft = 21,//1,
-21
-false
-''
-0
-#FnRight = 23,//3,
-23
-false
-''
-3000
-#VolUp = 4,
-#VolDown = 14,
-#Torch = 24,
-#SSwitchUp = 34,
-#SSwitchDown = 54,
-#SSwitchMid = 44>
\ No newline at end of file
D image/assets/profiles/template.kprof => image/assets/profiles/template.kprof +0 -84
@@ 1,84 0,0 @@
-#name of the profile
-example_profile
-#for each key following 4 variables must be defined
-#key code, in example 31
-#true if key is cyclic or false otherwise
-#characters assigned to the key
-#timeouts for each of the characters. 0 means that there is no timeout
-#NumericKey1 = 31,
-31
-false
-''
-0
-#NumericKey2 = 32,
-32
-true
-'a','b','c'
-0,0,0
-#NumericKey3 = 33,
-33
-true
-'d','e','f'
-0,0,0
-#NumericKey4 = 41,
-41
-true
-'g','h','i'
-0,0,0
-#NumericKey5 = 42,
-42
-true
-'j','k','l'
-0,0,0
-#NumericKey6 = 43,
-43
-true
-'m','n','o'
-0,0,0
-#NumericKey7 = 51,
-51
-true
-'p','q','r','s'
-0,0,0,0
-#NumericKey8 = 52,
-52
-true
-'t','u','v'
-0,0,0
-#NumericKey9 = 53,
-53
-true
-'w','x','y','z'
-0,0,0,0
-#NumericKey0 = 62,
-62
-false
-' '
-0
-#NumericKeyAst = 61,
-61
-false
-0x08
-0
-#NumericKeyPnd = 63,
-63
-false
-0x0A
-0
-#JoystickLeft = 11,
-#JoystickRight = 13,
-#JoystickUp = 2,
-#JoystickDown = 22,
-#JoystickEnter = 12,
-#FnLeft = 21,
-#FnRight = 23
-23
-false
-''
-2000
-#VolUp = 4,
-#VolDown = 14,
-#Torch = 24,
-#SSwitchUp = 34,
-#SSwitchDown = 54,
-#SSwitchMid = 44>
\ No newline at end of file
A image/user/db/notifications_002.sql => image/user/db/notifications_002.sql +3 -0
@@ 0,0 1,3 @@
+INSERT OR IGNORE INTO notifications (key, value) VALUES
+ ('1', '0'),
+ ('2', '0');
M module-apps/Application.cpp => module-apps/Application.cpp +3 -3
@@ 135,12 135,12 @@ namespace app
window->updateSignalStrength();
window->updateNetworkAccessTechnology();
- auto message = std::make_shared<sgui::DrawMessage>(window->buildDrawList(), mode);
+ auto message = std::make_shared<service::gui::DrawMessage>(window->buildDrawList(), mode);
if (shutdownInProgress) {
- message->setCommandType(sgui::DrawMessage::Type::SHUTDOWN);
+ message->setCommandType(service::gui::DrawMessage::Type::SHUTDOWN);
}
else if (suspendInProgress) {
- message->setCommandType(sgui::DrawMessage::Type::SUSPEND);
+ message->setCommandType(service::gui::DrawMessage::Type::SUSPEND);
}
sys::Bus::SendUnicast(message, service::name::gui, this);
}
M module-apps/application-desktop/data/Style.hpp => module-apps/application-desktop/data/Style.hpp +1 -1
@@ 9,7 9,7 @@ namespace style::desktop
namespace notifications
{
constexpr auto SpanSize = 8;
- constexpr auto DigitSize = 14;
+ constexpr auto DigitSize = 16;
constexpr auto IconWidth = 35;
constexpr auto TextMaxWidth = 250;
M module-apps/application-desktop/widgets/NotificationsBox.cpp => module-apps/application-desktop/widgets/NotificationsBox.cpp +2 -3
@@ 105,13 105,12 @@ namespace
auto number = new gui::Text();
if (indicator.length() > 2) {
number->setText(maxNotificationValue);
- number->setMinimumWidth(strlen(maxNotificationValue) * notifications::DigitSize);
}
else {
number->setText(indicator);
- number->setMinimumWidth(indicator.length() * notifications::DigitSize);
}
- number->setMinimumWidth(indicator.length() * notifications::DigitSize);
+
+ number->setMinimumWidth(number->getText().length() * notifications::DigitSize);
number->setFont(style::window::font::mediumbold);
number->setPenWidth(style::window::default_border_no_focus_w);
number->setMargins(gui::Margins(0, 0, style::window::default_right_margin, 0));
M module-apps/application-desktop/windows/PowerOffWindow.cpp => module-apps/application-desktop/windows/PowerOffWindow.cpp +18 -1
@@ 4,6 4,7 @@
#include "InputEvent.hpp"
#include "gui/widgets/BottomBar.hpp"
#include "gui/widgets/TopBar.hpp"
+#include "GuiTimer.hpp"
#include "log/log.hpp"
// module-utils
@@ 141,7 142,7 @@ namespace gui
infoLabel->setVisible(false);
application->refreshWindow(RefreshModes::GUI_REFRESH_DEEP);
-
+ scheduleSystemShutdown();
return true;
};
@@ 159,6 160,22 @@ namespace gui
};
}
+ // Temporary solution for shutting down the system.
+ // The former solution removed from service-gui during its refactor.
+ // To be reimplemented in SystemManager in upcoming commits.
+ void PowerOffWindow::scheduleSystemShutdown()
+ {
+ constexpr auto SystemShutdownDelayInMs = 500;
+ auto powerOffTimer = std::make_unique<app::GuiTimer>("PowerOffTimer", application);
+ powerOffTimer->setInterval(SystemShutdownDelayInMs);
+ timerCallback = [this](Item &, Timer &timer) {
+ detachTimer(timer);
+ sys::SystemManager::CloseSystem(application);
+ return true;
+ };
+ application->connect(std::move(powerOffTimer), this);
+ }
+
void PowerOffWindow::destroyInterface()
{
erase();
M module-apps/application-desktop/windows/PowerOffWindow.hpp => module-apps/application-desktop/windows/PowerOffWindow.hpp +2 -1
@@ 13,7 13,6 @@ namespace gui
{
class PowerOffWindow : public AppWindow
{
-
enum class State
{
PowerDown,
@@ 29,6 28,8 @@ namespace gui
gui::Image *powerDownImage = nullptr;
State state = State::Return;
+ void scheduleSystemShutdown();
+
public:
PowerOffWindow(app::Application *app);
void onBeforeShow(ShowMode mode, SwitchData *data) override;
M module-apps/application-messages/ApplicationMessages.cpp => module-apps/application-messages/ApplicationMessages.cpp +0 -1
@@ 65,7 65,6 @@ namespace app
if (msgl->messageType == MessageType::DBServiceNotification) {
auto msg = dynamic_cast<db::NotificationMessage *>(msgl);
- LOG_DEBUG("Received notification");
if (msg != nullptr) {
// window-specific actions
if (msg->interface == db::Interface::Name::SMSThread || msg->interface == db::Interface::Name::SMS) {
M module-apps/application-messages/widgets/SMSOutputWidget.cpp => module-apps/application-messages/widgets/SMSOutputWidget.cpp +0 -1
@@ 35,7 35,6 @@ namespace gui
smsBubble->setPenWidth(style::window::default_border_rect_no_focus);
smsBubble->setPadding(style::messages::smsOutput::sms_right_bubble_padding);
- LOG_DEBUG("ADD SMS TYPE: %d", static_cast<int>(record->type));
switch (record->type) {
case SMSType::QUEUED:
// Handle in the same way as case below. (pending sending display as already sent)
M module-apps/application-phonebook/windows/PhonebookMainWindow.cpp => module-apps/application-phonebook/windows/PhonebookMainWindow.cpp +1 -1
@@ 119,7 119,7 @@ namespace gui
void PhonebookMainWindow::HandleFilteringByLetter(const InputEvent &inputEvent)
{
auto code = translator.handle(inputEvent.key, inputMode ? inputMode->get() : "");
- if (code != KeyProfile::none_key) {
+ if (code != Profile::none_key) {
LOG_INFO("char=' %c'", static_cast<char>(code));
char letter = static_cast<char>(code);
std::string filterLetter;
M module-audio/Audio/Operation/PlaybackOperation.cpp => module-audio/Audio/Operation/PlaybackOperation.cpp +9 -3
@@ 57,7 57,7 @@ namespace audio
}
operationToken = token;
- assert(dataStreamOut);
+ assert(dataStreamOut != nullptr);
dec->startDecodingWorker(*dataStreamOut, endOfFileCallback);
@@ 89,17 89,19 @@ namespace audio
if (!audioDevice) {
return audio::RetCode::DeviceFailure;
}
+
+ dec->stopDecodingWorker();
return GetDeviceError(audioDevice->Stop());
}
audio::RetCode PlaybackOperation::Pause()
{
-
if (state == State::Paused || state == State::Idle) {
return RetCode::InvokedInIncorrectState;
}
-
state = State::Paused;
+
+ dec->stopDecodingWorker();
return GetDeviceError(audioDevice->Stop());
}
@@ 110,6 112,9 @@ namespace audio
return RetCode::InvokedInIncorrectState;
}
state = State::Active;
+
+ assert(dataStreamOut != nullptr);
+ dec->startDecodingWorker(*dataStreamOut, endOfFileCallback);
auto ret = audioDevice->Start(currentProfile->GetAudioFormat());
return GetDeviceError(ret);
}
@@ 196,6 201,7 @@ namespace audio
PlaybackOperation::~PlaybackOperation()
{
+ dec->stopDecodingWorker();
Stop();
dataStreamOut->reset();
dataStreamIn->reset();
M module-audio/Audio/Operation/RouterOperation.cpp => module-audio/Audio/Operation/RouterOperation.cpp +4 -11
@@ 25,17 25,6 @@ namespace audio
AddProfile(Profile::Type::RoutingHeadphones, PlaybackType::None, false);
AddProfile(Profile::Type::RoutingEarspeaker, PlaybackType::None, true);
AddProfile(Profile::Type::RoutingLoudspeaker, PlaybackType::None, true);
-
- auto defaultProfile = GetProfile(Profile::Type::RoutingEarspeaker);
- if (!defaultProfile) {
- throw AudioInitException("Error during initializing profile", RetCode::ProfileNotSet);
- }
- currentProfile = defaultProfile;
-
- auto retCode = SwitchToPriorityProfile();
- if (retCode != RetCode::Success) {
- throw AudioInitException("Failed to switch audio profile", retCode);
- }
}
audio::RetCode RouterOperation::SetOutputVolume(float vol)
@@ 153,7 142,11 @@ namespace audio
audio::RetCode RouterOperation::SwitchProfile(const audio::Profile::Type type)
{
auto ret = GetProfile(type);
+
if (ret) {
+ if (currentProfile && currentProfile->GetType() == ret->GetType()) {
+ return RetCode::Success;
+ }
currentProfile = ret;
}
else {
M module-audio/Audio/decoder/Decoder.cpp => module-audio/Audio/decoder/Decoder.cpp +8 -0
@@ 133,4 133,12 @@ namespace audio
}
}
+ void Decoder::stopDecodingWorker()
+ {
+ if (audioWorker) {
+ audioWorker->close();
+ }
+ audioWorker = nullptr;
+ }
+
} // namespace audio
M module-audio/Audio/decoder/Decoder.hpp => module-audio/Audio/decoder/Decoder.hpp +1 -0
@@ 81,6 81,7 @@ namespace audio
virtual uint32_t decode(uint32_t samplesToRead, int16_t *pcmData) = 0;
void startDecodingWorker(Stream &audioStream, DecoderWorker::EndOfFileCallback endOfFileCallback);
+ void stopDecodingWorker();
std::unique_ptr<Tags> fetchTags();
M module-bsp/board/linux/battery-charger/battery_charger.cpp => module-bsp/board/linux/battery-charger/battery_charger.cpp +6 -0
@@ 127,4 127,10 @@ namespace bsp
vTaskDelay(50);
}
}
+
+ std::uint16_t battery_getStatusRegister()
+ {
+ return static_cast<std::uint16_t>(batteryINTBSource::SOCOnePercentChange);
+ }
+
} // namespace bsp
M module-bsp/board/linux/lpm/LinuxLPM.cpp => module-bsp/board/linux/lpm/LinuxLPM.cpp +5 -0
@@ 24,4 24,9 @@ namespace bsp
{
currentFrequency = freq;
}
+
+ void LinuxLPM::SwitchOscillatorSource(bsp::LowPowerMode::OscillatorSource source)
+ {
+ currentOscSource = source;
+ }
} // namespace bsp
M module-bsp/board/linux/lpm/LinuxLPM.h => module-bsp/board/linux/lpm/LinuxLPM.h +1 -0
@@ 16,6 16,7 @@ namespace bsp
int32_t PowerOff() override final;
int32_t Reboot() override final;
void SetCpuFrequency(CpuFrequency freq) final;
+ void SwitchOscillatorSource(OscillatorSource source) final;
};
} // namespace bsp
M module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.cpp => module-bsp/board/rt1051/bsp/audio/RT1051Audiocodec.cpp +2 -2
@@ 99,11 99,11 @@ namespace bsp
return AudioDevice::RetCode::Failure;
}
- codec.Stop();
-
InStop();
OutStop();
+ codec.Stop();
+
state = State::Stopped;
vTaskDelay(codecSettleTime);
M module-bsp/board/rt1051/bsp/battery-charger/battery_charger.cpp => module-bsp/board/rt1051/bsp/battery-charger/battery_charger.cpp +31 -10
@@ 54,8 54,8 @@ static const uint8_t battery_DischargedPercent = 15;
static const uint8_t battery_maxTemperatureDegrees = 50;
static const uint8_t battery_minTemperatureDegrees = 5;
-static const uint16_t battery_maxVoltagemV = 4200;
-static const uint16_t battery_minVoltagemV = 3700;
+static constexpr inline uint16_t battery_maxVoltagemV = 4200;
+static constexpr inline uint16_t battery_minVoltagemV = 3600;
using namespace drivers;
@@ 96,7 96,7 @@ static bsp::batteryRetval battery_enableFuelGuageIRQs(void);
static bsp::batteryRetval battery_enableTopIRQs(void);
-static bsp::batteryRetval battery_disableAlerts(void);
+static bsp::batteryRetval battery_configureAlerts();
static void s_BSP_BatteryChargerIrqPinsInit();
@@ 139,7 139,27 @@ namespace bsp
AICL_I = (1 << 7),
};
- uint16_t battery_get_STATUS();
+ // CONFIG register bits
+ enum class B_CONFIG
+ {
+ Ber = 1 << 0,
+ Bei = 1 << 1,
+ Aen = 1 << 2,
+ FTHRM = 1 << 3,
+ ETHRM = 1 << 4,
+ SPR_5 = 1 << 5,
+ I2CSH = 1 << 6,
+ SHDN = 1 << 7,
+ Tex = 1 << 8,
+ Ten = 1 << 9,
+ AINSH = 1 << 10,
+ SPR_11 = 1 << 11,
+ Vs = 1 << 12,
+ Ts = 1 << 13,
+ Ss = 1 << 14,
+ SPR_15 = 1 << 15
+ };
+
uint16_t battery_get_CHG_INT_OK();
int battery_Init(xQueueHandle qHandle)
@@ 165,7 185,7 @@ namespace bsp
battery_setServiceVoltageThresholds(battery_maxVoltagemV, battery_minVoltagemV);
}
- battery_disableAlerts();
+ battery_configureAlerts();
battery_enableFuelGuageIRQs();
uint8_t level = 0;
@@ 222,7 242,7 @@ namespace bsp
}
}
- uint16_t battery_get_STATUS()
+ std::uint16_t battery_getStatusRegister()
{
uint16_t status = 0;
battery_fuelGaugeRead(bsp::batteryChargerRegisters::STATUS_REG, &status);
@@ 238,7 258,7 @@ namespace bsp
battery_chargerWrite(bsp::batteryChargerRegisters::CHG_INT_REG, 0);
}
- uint16_t status = battery_get_STATUS();
+ uint16_t status = battery_getStatusRegister();
if (status != 0) {
// write zero to clear irq source
battery_fuelGaugeWrite(bsp::batteryChargerRegisters::STATUS_REG, 0);
@@ 250,6 270,7 @@ namespace bsp
// write zero to clear interrupt source
battery_fuelGaugeWrite(bsp::batteryChargerRegisters::STATUS_REG, 0x0000);
}
+
} // namespace bsp
static int battery_fuelGaugeWrite(bsp::batteryChargerRegisters registerAddress, uint16_t value)
@@ 494,12 515,12 @@ static bsp::batteryRetval battery_enableFuelGuageIRQs(void)
return bsp::batteryRetval::battery_OK;
}
-static bsp::batteryRetval battery_disableAlerts(void)
+static bsp::batteryRetval battery_configureAlerts()
{
- uint16_t regVal = 0;
+ auto regVal = static_cast<std::uint16_t>(bsp::B_CONFIG::Aen); // Enable alerts
if (battery_fuelGaugeWrite(bsp::batteryChargerRegisters::CONFIG_REG, regVal) != kStatus_Success) {
- LOG_ERROR("battery_disableAlerts failed.");
+ LOG_ERROR("battery_configureAlerts failed.");
return bsp::batteryRetval::battery_ChargerError;
}
M module-bsp/board/rt1051/bsp/lpm/RT1051LPM.cpp => module-bsp/board/rt1051/bsp/lpm/RT1051LPM.cpp +43 -0
@@ 7,6 7,8 @@
#include "log/log.hpp"
#include "bsp/BoardDefinitions.hpp"
#include "bsp/watchdog/watchdog.hpp"
+#include <fsl_clock.h>
+#include <fsl_dcdc.h>
namespace bsp
{
@@ 66,4 68,45 @@ namespace bsp
}
LOG_INFO("CPU frequency changed to %lu", CLOCK_GetFreq(kCLOCK_CpuClk));
}
+
+ void RT1051LPM::SwitchOscillatorSource(bsp::LowPowerMode::OscillatorSource source)
+ {
+ if (source == bsp::LowPowerMode::OscillatorSource::Internal) {
+ if (IsClockEnabled(kCLOCK_Lpuart1) || IsClockEnabled(kCLOCK_Lpuart2) || IsClockEnabled(kCLOCK_Lpuart3) ||
+ IsClockEnabled(kCLOCK_Lpuart4) || IsClockEnabled(kCLOCK_Lpuart5) || IsClockEnabled(kCLOCK_Lpuart6) ||
+ IsClockEnabled(kCLOCK_Lpuart7) || IsClockEnabled(kCLOCK_Lpuart8)) {
+ return;
+ }
+
+ /// Switch DCDC to use DCDC internal OSC
+ DCDC_SetClockSource(DCDC, kDCDC_ClockInternalOsc);
+ /// Switch clock source to internal RC
+ CLOCK_SwitchOsc(kCLOCK_RcOsc);
+ CLOCK_DeinitExternalClk();
+ /// Wait CCM operation finishes
+ while (CCM->CDHIPR != 0) {}
+ }
+ else if (source == bsp::LowPowerMode::OscillatorSource::External) {
+ CLOCK_InitExternalClk(0);
+ /// Switch DCDC to use DCDC external OSC
+ DCDC_SetClockSource(DCDC, kDCDC_ClockExternalOsc);
+ /// Switch clock source to external OSC.
+ CLOCK_SwitchOsc(kCLOCK_XtalOsc);
+ /// Wait CCM operation finishes
+ while (CCM->CDHIPR != 0) {}
+ /// Set Oscillator ready counter value.
+ CCM->CCR = (CCM->CCR & (~CCM_CCR_OSCNT_MASK)) | CCM_CCR_OSCNT(bsp::OscillatorReadyCounterValue);
+ }
+
+ currentOscSource = source;
+ }
+
+ bool RT1051LPM::IsClockEnabled(clock_ip_name_t name) const noexcept
+ {
+ const auto index = static_cast<uint32_t>(name) >> CCM_TupleShift;
+ const auto shift = static_cast<uint32_t>(name) & CCM_TupleMask;
+
+ return ((*reinterpret_cast<volatile uint32_t *>(&CCM->CCGR0 + index)) & (ClockNeededRunWaitMode << shift));
+ }
+
} // namespace bsp
M module-bsp/board/rt1051/bsp/lpm/RT1051LPM.hpp => module-bsp/board/rt1051/bsp/lpm/RT1051LPM.hpp +8 -0
@@ 7,9 7,14 @@
#include "bsp/lpm/bsp_lpm.hpp"
#include "drivers/gpio/DriverGPIO.hpp"
#include "CpuFreqLPM.hpp"
+#include <fsl_clock.h>
namespace bsp
{
+ inline constexpr uint8_t OscillatorReadyCounterValue{127};
+ inline constexpr uint8_t CCM_TupleShift{8};
+ inline constexpr uint8_t CCM_TupleMask{0x1F};
+ inline constexpr uint32_t ClockNeededRunWaitMode{3};
class RT1051LPM : public LowPowerMode
{
@@ 18,8 23,11 @@ namespace bsp
int32_t PowerOff() override final;
int32_t Reboot() override final;
void SetCpuFrequency(CpuFrequency freq) final;
+ void SwitchOscillatorSource(OscillatorSource source) final;
private:
+ [[nodiscard]] bool IsClockEnabled(clock_ip_name_t name) const noexcept;
+
std::shared_ptr<drivers::DriverGPIO> gpio;
std::unique_ptr<bsp::CpuFreqLPM> CpuFreq;
};
M module-bsp/board/rt1051/common/board.cpp => module-bsp/board/rt1051/common/board.cpp +3 -3
@@ 6,7 6,7 @@ extern "C"
#include "fsl_clock.h"
#include "fsl_dcdc.h"
#include "pin_mux.h"
-#if LOG_REDIRECT == RTT_LUART
+#if LOG_LUART_ENABLED
#include "fsl_lpuart.h"
#endif
}
@@ 21,7 21,7 @@ extern std::uint8_t __sdram_cached_start[];
namespace bsp
{
-#if LOG_REDIRECT == RTT_LUART
+#if LOG_LUART_ENABLED
static lpuart_handle_t g_lpuartHandle;
#endif
@@ 46,7 46,7 @@ namespace bsp
/* Initialize debug console. */
static void BOARD_InitDebugConsole(void)
{
-#if LOG_REDIRECT == RTT_LUART
+#if LOG_LUART_ENABLED
/* The user initialization should be placed here */
lpuart_config_t lpuartConfig;
M module-bsp/bsp/battery-charger/battery_charger.hpp => module-bsp/bsp/battery-charger/battery_charger.hpp +9 -0
@@ 8,6 8,7 @@
#ifndef MODULE_BSP_BSP_BATTERY_CHARGER_BATTERY_CHARGER_HPP_
#define MODULE_BSP_BSP_BATTERY_CHARGER_BATTERY_CHARGER_HPP_
+#include <cstdint>
namespace bsp{
enum class batteryChargerRegisters{
@@ 100,6 101,12 @@ namespace bsp{
INOKB = 0x02
};
+ enum class batteryINTBSource{
+ minSOCAlert = 1 << 10,
+ minVAlert = 1 << 8,
+ SOCOnePercentChange = 1 << 7
+ };
+
int battery_Init(xQueueHandle qHandle);
void battery_Deinit(void);
@@ 111,6 118,8 @@ namespace bsp{
void battery_ClearAllIRQs(void);
void battery_clearFuelGuageIRQ(void);
+
+ std::uint16_t battery_getStatusRegister();
}
BaseType_t BSP_BatteryChargerINOKB_IRQHandler();
M module-bsp/bsp/lpm/bsp_lpm.cpp => module-bsp/bsp/lpm/bsp_lpm.cpp +5 -0
@@ 30,4 30,9 @@ namespace bsp{
{
return currentFrequency;
}
+
+ LowPowerMode::OscillatorSource LowPowerMode::GetCurrentOscillatorSource() const noexcept
+ {
+ return currentOscSource;
+ }
}
M module-bsp/bsp/lpm/bsp_lpm.hpp => module-bsp/bsp/lpm/bsp_lpm.hpp +10 -0
@@ 18,6 18,11 @@ namespace bsp {
Level_5, // 264 MHz
Level_6 // 528 MHz
};
+ enum class OscillatorSource
+ {
+ External,
+ Internal
+ };
LowPowerMode() = default;
virtual ~LowPowerMode() = default;
@@ 26,11 31,16 @@ namespace bsp {
virtual int32_t PowerOff() = 0;
virtual int32_t Reboot() = 0;
+
virtual void SetCpuFrequency(CpuFrequency freq) = 0;
[[nodiscard]] CpuFrequency GetCurrentFrequency() const noexcept;
+ virtual void SwitchOscillatorSource(OscillatorSource source) = 0;
+ [[nodiscard]] OscillatorSource GetCurrentOscillatorSource() const noexcept;
+
protected:
CpuFrequency currentFrequency = CpuFrequency::Level_6;
+ OscillatorSource currentOscSource = OscillatorSource::External;
};
} // namespace bsp
M module-cellular/CMakeLists.txt => module-cellular/CMakeLists.txt +1 -0
@@ 31,6 31,7 @@ set(SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcCmti.cpp
${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcClip.cpp
${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcCpin.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcQiurc.cpp
${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcPoweredDown.cpp
${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcResponse.cpp
${CMAKE_CURRENT_SOURCE_DIR}/at/src/UrcFactory.cpp
M module-cellular/at/Commands.hpp => module-cellular/at/Commands.hpp +8 -2
@@ 151,7 151,10 @@ namespace at
COLP_GET,
COLP_ENABLE,
COLP_DISABLE,
- CSSN, /// Supplementary Services - Supplementary Service Notifications
+ CSSN, /// Supplementary Services - Supplementary Service Notifications
+ QICSGP, /// Configure Parameters of a TCP/IP Context
+ QIACT, /// Activate a PDP Context
+ QIDEACT /// Deactivate a PDP Context
};
// below timeouts are defined in Quectel_EC25&EC21_AT_Commands_Manual_V1.3.pdf
@@ 248,7 251,10 @@ namespace at
{AT::COLP_GET, {"AT+COLP?", default_long_doc_timeout}},
{AT::COLP_ENABLE, {"AT+COLP=1", default_long_doc_timeout}},
{AT::COLP_DISABLE, {"AT+COLP=0", default_long_doc_timeout}},
- {AT::CSSN, {"AT+CSSN=\"", default_doc_timeout}}};
+ {AT::CSSN, {"AT+CSSN=\"", default_doc_timeout}},
+ {AT::QICSGP, {"AT+QICSGP", default_timeout}},
+ {AT::QIACT, {"AT+QIACT", 150000}},
+ {AT::QIDEACT, {"AT+QIDEACT", 40000}}};
if (fact.count(at)) {
return fact.at(at);
M module-cellular/at/UrcClip.hpp => module-cellular/at/UrcClip.hpp +1 -1
@@ 31,7 31,7 @@ namespace at::urc
};
static constexpr std::string_view head = "+CLIP";
- static bool isURC(const std::string uHead)
+ static bool isURC(const std::string &uHead)
{
return uHead.find(Clip::head) != std::string::npos;
}
M module-cellular/at/UrcCmti.hpp => module-cellular/at/UrcCmti.hpp +1 -1
@@ 17,7 17,7 @@ namespace at::urc
public:
static constexpr std::string_view head = "+CMTI";
- static auto isURC(const std::string uHead) -> bool
+ static auto isURC(const std::string &uHead) -> bool
{
return uHead.find(Cmti::head) != std::string::npos;
}
M module-cellular/at/UrcCpin.hpp => module-cellular/at/UrcCpin.hpp +1 -1
@@ 43,7 43,7 @@ namespace at::urc
static constexpr auto head = "+CPIN";
- static bool isURC(const std::string uHead)
+ static bool isURC(const std::string &uHead)
{
return uHead.find(Cpin::head) != std::string::npos;
}
M module-cellular/at/UrcCreg.hpp => module-cellular/at/UrcCreg.hpp +1 -1
@@ 24,7 24,7 @@ namespace at::urc
public:
static constexpr std::string_view head = "+CREG";
- static bool isURC(const std::string uHead)
+ static bool isURC(const std::string &uHead)
{
return uHead.find(Creg::head) != std::string::npos;
}
M module-cellular/at/UrcCtze.hpp => module-cellular/at/UrcCtze.hpp +1 -1
@@ 20,7 20,7 @@ namespace at::urc
public:
static constexpr std::string_view head = "+CTZE";
- static auto isURC(const std::string uHead) -> bool
+ static auto isURC(const std::string &uHead) -> bool
{
return uHead.find(Ctze::head) != std::string::npos;
}
M module-cellular/at/UrcCusd.hpp => module-cellular/at/UrcCusd.hpp +1 -1
@@ 32,7 32,7 @@ namespace at::urc
};
Cusd(const std::string &urcBody, const std::string &urcHead = std::string());
static constexpr std::string_view head = "+CUSD";
- static auto isURC(const std::string uHead) -> bool
+ static auto isURC(const std::string &uHead) -> bool
{
return uHead.find(Cusd::head) != std::string::npos;
}
M module-cellular/at/UrcHandler.hpp => module-cellular/at/UrcHandler.hpp +2 -0
@@ 12,6 12,7 @@ namespace at::urc
class Ctze;
class Qind;
class Cpin;
+ class Qiurc;
class PoweredDown;
class UrcResponse;
@@ 25,6 26,7 @@ namespace at::urc
virtual void Handle(Ctze &urc) = 0;
virtual void Handle(Qind &urc) = 0;
virtual void Handle(Cpin &urc) = 0;
+ virtual void Handle(Qiurc &urc) = 0;
virtual void Handle(PoweredDown &urc) = 0;
virtual void Handle(UrcResponse &urc) = 0;
};
M module-cellular/at/UrcPoweredDown.hpp => module-cellular/at/UrcPoweredDown.hpp +1 -1
@@ 12,7 12,7 @@ namespace at::urc
public:
static constexpr std::string_view head_immediate = "POWERED DOWN";
static constexpr std::string_view head_normal = "NORMAL POWER DOWN";
- static auto isURC(const std::string uHead) -> bool
+ static auto isURC(const std::string &uHead) -> bool
{
auto isImmediatePowerDown = uHead.find(PoweredDown::head_immediate) != std::string::npos;
auto isNormalPowerDown = uHead.find(PoweredDown::head_normal) != std::string::npos;
M module-cellular/at/UrcQind.hpp => module-cellular/at/UrcQind.hpp +1 -1
@@ 48,7 48,7 @@ namespace at::urc
};
static constexpr std::string_view head = "+QIND";
- static auto isURC(const std::string uHead) -> bool
+ static auto isURC(const std::string &uHead) -> bool
{
return uHead.find(Qind::head) != std::string::npos;
}
A module-cellular/at/UrcQiurc.hpp => module-cellular/at/UrcQiurc.hpp +54 -0
@@ 0,0 1,54 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "Urc.hpp"
+
+namespace at::urc
+{
+
+ /// +QIURC: <action>,[p1], [p2] - multipurpose URC information (in general for TCP connection)
+ class Qiurc : public Urc
+ {
+ /**
+ * In general could be at last one param in +QIURC: "incoming full"
+ * to such with CR LF messages contain data +QIURC:
+ * "recv",<connectID>,<currentrecvlength>,<remoteIP>,<remote_port><CR><LF><data>
+ */
+ enum Tokens
+ {
+ Type = 0,
+ FirstParam = 1
+ };
+
+ public:
+ enum class QIUrcMessages
+ {
+ DeactivateContext = 1
+ };
+ static constexpr auto qiurcPdpdeact = "pdpdeact"; ///< +QIURC:"pdpdeact",<contextID>
+ static constexpr auto qiurcPdpdeactCount = 2;
+
+ static constexpr auto head = "+QIURC";
+
+ static bool isURC(const std::string &uHead)
+ {
+ return uHead.find(Qiurc::head) != std::string::npos;
+ }
+
+ using Urc::Urc;
+
+ [[nodiscard]] auto isValid() const noexcept -> bool override;
+
+ [[nodiscard]] auto getType() const noexcept -> std::optional<QIUrcMessages>;
+
+ [[nodiscard]] auto getFirstParam() const noexcept -> std::optional<std::string>;
+
+ void Handle(UrcHandler &h) final
+ {
+ h.Handle(*this);
+ }
+ };
+
+} // namespace at::urc
M module-cellular/at/UrcResponse.hpp => module-cellular/at/UrcResponse.hpp +1 -1
@@ 23,7 23,7 @@ namespace at::urc
NoAnswer
};
- static auto isURC(const std::string uHead) -> std::optional<URCResponseType>
+ static auto isURC(const std::string &uHead) -> std::optional<URCResponseType>
{
for (auto &resp : urcResponses) {
if (uHead.find(resp.second) != std::string::npos) {
M module-cellular/at/response.cpp => module-cellular/at/response.cpp +64 -5
@@ 11,7 11,6 @@ namespace at
{
namespace response
{
- constexpr auto StringDelimiter = "\"";
std::optional<std::string> getResponseLineATCommand(const at::Result &resp, std::string_view head)
{
@@ 36,7 35,23 @@ namespace at
}
return std::nullopt;
}
+ std::optional<ResponseTokens> getTokensForATResults(const at::Result &resp, std::string_view head)
+ {
+ if (resp.code != at::Result::Code::OK)
+ return std::nullopt;
+
+ std::vector<std::vector<std::string>> parts;
+ for (auto el : resp.response) {
+ if (el.compare(0, head.length(), head) == 0) {
+ auto body = el.substr(head.length());
+ parts.push_back(utils::split(body, ","));
+ }
+ }
+
+ return parts;
+ }
+ constexpr std::string_view AT_COPS = "+COPS:";
bool parseCOPS(const at::Result &resp, std::vector<cops::Operator> &ret)
{
/// +COPS: (list of supported <stat>,long alphanumeric <oper>,
@@ 53,7 68,6 @@ namespace at
constexpr auto minOperatorParams = 4;
constexpr auto maxOperatorParams = 5;
- constexpr std::string_view AT_COPS = "+COPS:";
if (auto line = getResponseLineATCommand(resp, AT_COPS); line) {
const auto &commandLine = *line;
@@ 98,15 112,60 @@ namespace at
return false;
}
+
+ bool parseCOPS(const at::Result &resp, cops::CurrentOperatorInfo &ret)
+ {
+ /// ret as +COPS: <mode>[,<format>[,<oper>][,<Act>]]
+ /// parameters could be 1,2,3,4 all optional in documentation !
+
+ constexpr auto minCOPSLength = 1;
+
+ if (auto line = getResponseLineATCommand(resp, AT_COPS); line) {
+ const auto &commandLine = *line;
+
+ if (commandLine.length() < minCOPSLength) {
+ return false;
+ }
+
+ auto opParams = utils::split(commandLine, ",");
+ cops::Operator op;
+
+ switch (opParams.size()) {
+ case 4:
+ op.technology = static_cast<cops::AccessTechnology>(utils::getNumericValue<int>(opParams[3]));
+ [[fallthrough]];
+ case 3: {
+ ret.setFormat(static_cast<cops::NameFormat>(utils::getNumericValue<int>(opParams[1])));
+ utils::findAndReplaceAll(opParams[2], at::response::StringDelimiter, "");
+ op.setNameByFormat(ret.getFormat(), opParams[2]);
+ }
+ ret.setOperator(op);
+ [[fallthrough]];
+ case 2:
+ ret.setFormat(static_cast<cops::NameFormat>(utils::getNumericValue<int>(opParams[1])));
+ [[fallthrough]];
+ case 1:
+ ret.setMode(static_cast<cops::CopsMode>(utils::getNumericValue<int>(opParams[0])));
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+ }
+ return false;
+ }
+
bool parseQPINC(const at::Result &resp, qpinc::AttemptsCounters &ret)
{
/// parse only first result from QPINC
const std::string_view AT_QPINC_SC = "+QPINC:";
if (auto tokens = getTokensForATCommand(resp, AT_QPINC_SC); tokens) {
constexpr int QPINC_TokensCount = 3;
- if ((*tokens).size() == QPINC_TokensCount) {
- utils::toNumeric((*tokens)[1], ret.PinCounter);
- utils::toNumeric((*tokens)[2], ret.PukCounter);
+ auto pinc_tokens = (*tokens);
+ if (pinc_tokens.size() == QPINC_TokensCount) {
+ utils::toNumeric(pinc_tokens[1], ret.PinCounter);
+ utils::toNumeric(pinc_tokens[2], ret.PukCounter);
return true;
}
}
M module-cellular/at/response.hpp => module-cellular/at/response.hpp +110 -0
@@ 15,6 15,7 @@ namespace at
{
namespace response
{
+ constexpr auto StringDelimiter = "\"";
namespace qpinc
{
/// Structure that holds parsed information from AT+QPINC command
@@ 54,6 55,12 @@ namespace at
E_UTRAN = 7,
CDMA = 100
};
+ enum class NameFormat
+ {
+ Long = 0,
+ Short = 1,
+ Numeric = 2
+ };
class Operator
{
@@ 63,13 70,116 @@ namespace at
std::string longName;
std::string numericName;
std::optional<cops::AccessTechnology> technology = std::nullopt;
+
+ std::string getNameByFormat(NameFormat format)
+ {
+ switch (format) {
+ case NameFormat::Long:
+ return longName;
+ case NameFormat::Short:
+ return shortName;
+ case NameFormat::Numeric:
+ return numericName;
+ }
+ return {};
+ }
+
+ void setNameByFormat(NameFormat format, const std::string &name)
+ {
+ switch (format) {
+ case NameFormat::Long:
+ longName = name;
+ break;
+ case NameFormat::Short:
+ shortName = name;
+ break;
+ case NameFormat::Numeric:
+ numericName = name;
+ break;
+ }
+ }
+ };
+
+ class CurrentOperatorInfo
+ {
+ Operator op;
+ CopsMode mode = CopsMode::Automatic;
+ NameFormat format = NameFormat::Long;
+ bool operatorSet = false;
+
+ public:
+ void setFormat(NameFormat format)
+ {
+ this->format = format;
+ }
+ NameFormat getFormat() const noexcept
+ {
+ return this->format;
+ }
+ void setMode(CopsMode mode)
+ {
+ this->mode = mode;
+ }
+ CopsMode getMode() const noexcept
+ {
+ return this->mode;
+ }
+
+ void setOperator(Operator op)
+ {
+ this->operatorSet = true;
+ this->op = op;
+ }
+ std::optional<cops::Operator> getOperator() const
+ {
+ if (operatorSet) {
+ return op;
+ }
+ else {
+ return std::nullopt;
+ }
+ }
};
} // namespace cops
+ /**
+ * @brief parse for AT+COPS=? from quectel
+ *
+ */
bool parseCOPS(const at::Result &resp, std::vector<cops::Operator> &ret);
+ using ResponseTokens = std::vector<std::vector<std::string>>;
+ /**
+ * @brief parse for AT+COPS? from quectel
+ *
+ */
+ bool parseCOPS(const at::Result &resp, cops::CurrentOperatorInfo &ret);
std::vector<std::string> tokenize(std::string &response, std::string separator = ",");
+
+ /**
+ * For AT one line (+XYZ) response like:
+ * +CPIN READY
+ * OK
+ */
std::optional<std::vector<std::string>> getTokensForATCommand(const at::Result &resp, std::string_view head);
+
+ /**
+ * For AT multiline response like (last OK)
+ * +QIACT:1,<context_state>,<context_type>[,<IP_address>]
+ * [.....
+ * +QIACT:16,<context_state>,<context_type>[,<IP_address>]]
+ * OK
+ *
+ * response from function like QPING (not mention in DOC as URC), looks like (first OK)
+ * OK
+ * +QPING: 0,"61.135.169.125",32,192,255
+ * +QPING: 0,"61.135.169.125",32,240,255
+ * ...
+ * +QPING: 0,4,4,0,192,479,287
+ *
+ * Warning: should not be used for URC !
+ */
+ std::optional<ResponseTokens> getTokensForATResults(const at::Result &resp, std::string_view head);
bool parseCSQ(std::string response, std::string &result);
bool parseCSQ(std::string cellularResponse, uint32_t &result);
bool parseCREG(std::string &response, uint32_t &result);
M module-cellular/at/src/UrcFactory.cpp => module-cellular/at/src/UrcFactory.cpp +4 -0
@@ 12,6 12,7 @@
#include <UrcPoweredDown.hpp>
#include <UrcResponse.hpp>
#include <UrcCpin.hpp>
+#include <UrcQiurc.hpp>
using namespace at::urc;
std::unique_ptr<Urc> UrcFactory::Create(const std::string &urcMessage)
@@ 51,6 52,9 @@ std::unique_ptr<Urc> UrcFactory::Create(const std::string &urcMessage)
else if (auto type = UrcResponse::isURC(head)) {
return std::make_unique<UrcResponse>(type.value());
}
+ else if (Qiurc::isURC(head)) {
+ return std::make_unique<Qiurc>(body);
+ }
return std::make_unique<Urc>(body, head);
}
A module-cellular/at/src/UrcQiurc.cpp => module-cellular/at/src/UrcQiurc.cpp +33 -0
@@ 0,0 1,33 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "UrcQiurc.hpp"
+
+using namespace at::urc;
+
+auto Qiurc::isValid() const noexcept -> bool
+{
+ return tokens.size() == qiurcPdpdeactCount; /// only support one message type
+}
+
+auto Qiurc::getType() const noexcept -> std::optional<QIUrcMessages>
+{
+ if (!isValid()) {
+ return std::nullopt;
+ }
+
+ if (tokens[Tokens::Type] == qiurcPdpdeact) {
+ return QIUrcMessages::DeactivateContext;
+ }
+ return std::nullopt;
+}
+
+auto Qiurc::getFirstParam() const noexcept -> std::optional<std::string>
+{
+ if (getType()) {
+ if (isValid() && (*getType() == QIUrcMessages::DeactivateContext)) {
+ return tokens[Tokens::FirstParam];
+ }
+ }
+ return std::nullopt;
+}
M module-cellular/test/unittest_URC.cpp => module-cellular/test/unittest_URC.cpp +33 -0
@@ 17,6 17,7 @@
#include "UrcCmti.hpp"
#include "UrcClip.hpp"
#include "UrcCpin.hpp"
+#include "UrcQiurc.hpp"
#include "UrcPoweredDown.hpp"
#include "UrcResponse.hpp"
#include "UrcFactory.hpp"
@@ 808,3 809,35 @@ TEST_CASE("+Qind: SMS DONE")
REQUIRE(qind->isSmsDone());
}
}
+
+TEST_CASE("+Qiurc: TCP Context and connection message")
+{
+ SECTION("PDP Context deactivate - normal message")
+ {
+ /// +QIURC:"pdpdeact",<contextID>
+
+ auto urc = at::urc::UrcFactory::Create("+QIURC: \"pdpdeact\",1");
+ auto qiurc = getURC<at::urc::Qiurc>(urc);
+
+ REQUIRE(qiurc);
+ REQUIRE(qiurc->getType());
+ REQUIRE(qiurc->isValid());
+ REQUIRE(*qiurc->getType() == at::urc::Qiurc::QIUrcMessages::DeactivateContext);
+ REQUIRE(*qiurc->getFirstParam() == "1");
+ }
+
+ SECTION("PDP Context deactivate - corrupted, but OK format")
+ {
+ auto urc = at::urc::UrcFactory::Create("+QIURC:\"pdpdeactwww\",1");
+ auto qiurc = getURC<at::urc::Qiurc>(urc);
+ REQUIRE(qiurc);
+ REQUIRE(qiurc->getType() == std::nullopt);
+ }
+
+ SECTION("PDP Context deactivate - wrong param count (in case simple implementation)")
+ {
+ auto urc = at::urc::UrcFactory::Create("+QIURC:\"pdpdeactwww\",1,3");
+ auto qiurc = getURC<at::urc::Qiurc>(urc);
+ REQUIRE(qiurc->getType() == std::nullopt);
+ }
+}
M module-cellular/test/unittest_response.cpp => module-cellular/test/unittest_response.cpp +55 -0
@@ 96,4 96,59 @@ TEST_CASE("Response COPS")
resp.response.push_back("+COPS: (2,\"PLAY\",\"PLAY\", 4)(");
REQUIRE(at::response::parseCOPS(resp, ret) == false);
}
+
+ SECTION("OK COPS? - return operator")
+ {
+ at::Result resp;
+ resp.code = at::Result::Code::OK;
+ at::response::cops::CurrentOperatorInfo ret;
+ resp.response.push_back("+COPS: 0,0,\"PLAY\",2");
+ resp.response.push_back("OK");
+ REQUIRE(resp.code == at::Result::Code::OK);
+ REQUIRE(at::response::parseCOPS(resp, ret) == true);
+ REQUIRE(ret.getOperator());
+ REQUIRE(ret.getMode() == at::response::cops::CopsMode::Automatic);
+ REQUIRE(ret.getFormat() == at::response::cops::NameFormat::Long);
+ auto op = *ret.getOperator();
+ REQUIRE(op.longName == "PLAY");
+ REQUIRE(op.technology == at::response::cops::AccessTechnology::UTRAN);
+ }
+
+ SECTION("OK COPS? - return operator no act")
+ {
+ at::Result resp;
+ resp.code = at::Result::Code::OK;
+ at::response::cops::CurrentOperatorInfo ret;
+ resp.response.push_back("+COPS: 0,0,\"PLAY\"");
+ resp.response.push_back("OK");
+ REQUIRE(resp.code == at::Result::Code::OK);
+ REQUIRE(at::response::parseCOPS(resp, ret) == true);
+ REQUIRE(ret.getOperator());
+ REQUIRE(ret.getMode() == at::response::cops::CopsMode::Automatic);
+ REQUIRE(ret.getFormat() == at::response::cops::NameFormat::Long);
+ auto op = *ret.getOperator();
+ REQUIRE(op.longName == "PLAY");
+ }
+
+ SECTION("OK COPS? - no operator")
+ {
+ at::Result resp;
+ resp.code = at::Result::Code::OK;
+ at::response::cops::CurrentOperatorInfo ret;
+ resp.response.push_back("+COPS: 0");
+ resp.response.push_back("OK");
+ REQUIRE(resp.code == at::Result::Code::OK);
+ REQUIRE(at::response::parseCOPS(resp, ret) == true);
+ REQUIRE(ret.getMode() == at::response::cops::CopsMode::Automatic);
+ }
+ SECTION("WRONG COPS? - to many")
+ {
+ at::Result resp;
+ resp.code = at::Result::Code::OK;
+ at::response::cops::CurrentOperatorInfo ret;
+ resp.response.push_back("+COPS: 0,0,\"PLAY\",2, 3");
+ resp.response.push_back("OK");
+ REQUIRE(resp.code == at::Result::Code::OK);
+ REQUIRE(at::response::parseCOPS(resp, ret) == false);
+ }
}
A module-db/Common/Logging.hpp => module-db/Common/Logging.hpp +12 -0
@@ 0,0 1,12 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#if DEBUG_DB_MODEL_DATA == 1
+#define debug_db_data(...) LOG_DEBUG(__VA_ARGS__)
+#define error_db_data(...) LOG_ERROR(__VA_ARGS__)
+#else
+#define debug_db_data(...)
+#define error_db_data(...)
+#endif
M module-db/Interface/ContactRecord.cpp => module-db/Interface/ContactRecord.cpp +51 -50
@@ 35,7 35,7 @@ auto ContactRecordInterface::Add(ContactRecord &rec) -> bool
}
uint32_t contactID = contactDB->getLastInsertRowId();
- LOG_DEBUG("New contact with ID %" PRIu32 " created", contactID);
+ debug_db_data("New contact with ID %" PRIu32 " created", contactID);
rec.ID = contactID;
ret = contactDB->name.add(ContactsNameTableRow{{.ID = DB_ID_NONE},
@@ 128,7 128,7 @@ auto ContactRecordInterface::RemoveByID(uint32_t id) -> bool
auto ContactRecordInterface::runQuery(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
{
if (query->type != db::Query::Type::Read) {
- LOG_WARN("Received unhandled query type: %lu", static_cast<unsigned long int>(query->type));
+ debug_db_data("Received unhandled query type: %lu", static_cast<unsigned long int>(query->type));
return nullptr;
}
@@ 157,7 157,7 @@ auto ContactRecordInterface::runQuery(std::shared_ptr<db::Query> query) -> std::
return numberGetByIdQuery(query);
}
else {
- LOG_ERROR("Unexpected query type.");
+ error_db_data("Unexpected query type.");
return nullptr;
}
}
@@ 169,14 169,14 @@ auto ContactRecordInterface::getQuery(std::shared_ptr<db::Query> query) -> std::
bool searchByNumber = false;
if (textFilter != nullptr && textFilter->isFilterPresent() && utils::is_number(textFilter->getFilterData())) {
searchByNumber = true;
- LOG_INFO("Filtering by number: %s", textFilter->getFilterData().c_str());
+ debug_db_data("Filtering by number: %s", textFilter->getFilterData().c_str());
}
auto readQuery = static_cast<const db::query::ContactGet *>(query.get());
- LOG_DEBUG("Contact read query, filter: \"%s\", offset=%lu, limit=%lu",
- readQuery->getFilterData().c_str(),
- static_cast<unsigned long>(readQuery->getOffset()),
- static_cast<unsigned long>(readQuery->getLimit()));
+ debug_db_data("Contact read query, filter: \"%s\", offset=%lu, limit=%lu",
+ readQuery->getFilterData().c_str(),
+ static_cast<unsigned long>(readQuery->getOffset()),
+ static_cast<unsigned long>(readQuery->getLimit()));
auto [limit, offset] = readQuery->getLimitOffset();
auto matchType = searchByNumber ? ContactsTable::MatchType::TextNumber : ContactsTable::MatchType::Name;
uint32_t groupID = readQuery->getGroupFilterData();
@@ 186,7 186,7 @@ auto ContactRecordInterface::getQuery(std::shared_ptr<db::Query> query) -> std::
else {
groupID = favouritesGroupId;
}
- LOG_DEBUG("Contact match Type: %lu", static_cast<unsigned long int>(matchType));
+ debug_db_data("Contact match Type: %lu", static_cast<unsigned long int>(matchType));
std::vector<std::uint32_t> ids;
@@ 196,7 196,7 @@ auto ContactRecordInterface::getQuery(std::shared_ptr<db::Query> query) -> std::
else {
ids = contactDB->contacts.GetIDsSortedByName(limit, offset);
}
- LOG_DEBUG("Received records: %lu", static_cast<unsigned long int>(ids.size()));
+ debug_db_data("Received records: %lu", static_cast<unsigned long int>(ids.size()));
std::vector<ContactRecord> result(ids.size());
std::transform(std::begin(ids), std::end(ids), std::begin(result), [this](uint32_t id) { return GetByID(id); });
@@ 240,11 240,11 @@ auto ContactRecordInterface::getSizeQuery(std::shared_ptr<db::Query> query) -> s
bool searchByNumber = false;
if (textFilter != nullptr && textFilter->isFilterPresent() && utils::is_number(textFilter->getFilterData())) {
searchByNumber = true;
- LOG_INFO("Filtering by number: %s", textFilter->getFilterData().c_str());
+ debug_db_data("Filtering by number: %s", textFilter->getFilterData().c_str());
}
auto countQuery = static_cast<const db::query::ContactGetSize *>(query.get());
- LOG_DEBUG("Contact count query, filter: \"%s\"", countQuery->getFilterData().c_str());
+ debug_db_data("Contact count query, filter: \"%s\"", countQuery->getFilterData().c_str());
std::size_t count = 0;
if (!countQuery->isFilterPresent()) {
@@ 281,7 281,7 @@ auto ContactRecordInterface::getSizeQuery(std::shared_ptr<db::Query> query) -> s
count = contactDB->name.GetCountByName(countQuery->getFilterData());
}
- LOG_DEBUG("Contact count query result: %lu", static_cast<unsigned long>(count));
+ debug_db_data("Contact count query result: %lu", static_cast<unsigned long>(count));
auto response = std::make_unique<db::query::RecordsSizeQueryResult>(count);
response->setRequestQuery(query);
@@ 361,7 361,7 @@ auto ContactRecordInterface::unbindNumber(std::uint32_t contactId, std::uint32_t
auto ContactRecordInterface::Update(const ContactRecord &rec) -> bool
{
- LOG_DEBUG("%s", __FUNCTION__);
+ debug_db_data("%s", __FUNCTION__);
bool ret;
ContactsTableRow contact = contactDB->contacts.getByIdWithTemporary(rec.ID);
if (!contact.isValid()) {
@@ 381,7 381,7 @@ auto ContactRecordInterface::Update(const ContactRecord &rec) -> bool
.numberUser = number.number.getEntered().c_str(),
.numbere164 = number.number.getE164().c_str(),
.type = number.numberType})) {
- LOG_ERROR("Failed to add new number for contact");
+ error_db_data("Failed to add new number for contact");
return false;
}
@@ 390,7 390,7 @@ auto ContactRecordInterface::Update(const ContactRecord &rec) -> bool
else {
if (auto oldId = numberMatch->getContactID(); oldId != rec.ID) {
if (!unbindNumber(oldId, numberMatch->getNumberID())) {
- LOG_ERROR(
+ error_db_data(
"Failed to unbind number %" PRIu32 " from contact %" PRIu32, numberMatch->getNumberID(), oldId);
}
@@ 398,9 398,9 @@ auto ContactRecordInterface::Update(const ContactRecord &rec) -> bool
numberRecord.contactID = rec.ID;
if (!contactDB->number.update(numberRecord)) {
- LOG_ERROR("Failed to reassing number %" PRIu32 " to contact %" PRIu32,
- numberMatch->getNumberID(),
- rec.ID);
+ error_db_data("Failed to reassing number %" PRIu32 " to contact %" PRIu32,
+ numberMatch->getNumberID(),
+ rec.ID);
}
}
outputNumberIDs.push_back(numberMatch->getNumberID());
@@ 411,9 411,9 @@ auto ContactRecordInterface::Update(const ContactRecord &rec) -> bool
for (auto inNumberID : inputNumberIDs) {
if (std::find(std::begin(outputNumberIDs), std::end(outputNumberIDs), inNumberID) ==
std::end(outputNumberIDs)) {
- LOG_INFO("Removing obsolete number from table: %" PRIu32, inNumberID);
+ debug_db_data("Removing obsolete number from table: %" PRIu32, inNumberID);
if (!contactDB->number.removeById(inNumberID)) {
- LOG_ERROR("Failed to remove number");
+ error_db_data("Failed to remove number");
return false;
}
}
@@ 423,12 423,12 @@ auto ContactRecordInterface::Update(const ContactRecord &rec) -> bool
auto speedDialContacts = GetBySpeedDial(rec.speeddial);
if (!speedDialContacts->empty()) {
if (speedDialContacts->size() != 1) {
- LOG_ERROR("Multiple contacts for same speed dial value %s", rec.speeddial.c_str());
+ error_db_data("Multiple contacts for same speed dial value %s", rec.speeddial.c_str());
}
auto oldContact = contactDB->contacts.getById(speedDialContacts->at(0).ID);
oldContact.speedDial = "";
if (!contactDB->contacts.update(oldContact)) {
- LOG_ERROR("Failed to remove speed dial from old contact");
+ error_db_data("Failed to remove speed dial from old contact");
}
}
@@ 442,7 442,7 @@ auto ContactRecordInterface::Update(const ContactRecord &rec) -> bool
.nameAlternative = rec.alternativeName});
if (!ret) {
- LOG_ERROR("Failed to update contact.");
+ error_db_data("Failed to update contact.");
return false;
}
@@ 452,7 452,7 @@ auto ContactRecordInterface::Update(const ContactRecord &rec) -> bool
.nameAlternative = rec.alternativeName});
if (!ret) {
- LOG_ERROR("Failed to update contact name");
+ error_db_data("Failed to update contact name");
return false;
}
@@ 463,14 463,14 @@ auto ContactRecordInterface::Update(const ContactRecord &rec) -> bool
.mail = rec.mail});
if (!ret) {
- LOG_ERROR("Failed to update contact address");
+ error_db_data("Failed to update contact address");
return false;
}
ret = contactDB->ringtones.update(ContactsRingtonesTableRow(contact.ID, rec.assetPath));
if (!ret) {
- LOG_ERROR("Failed to update contact ringtone");
+ error_db_data("Failed to update contact ringtone");
return false;
}
@@ 487,7 487,7 @@ auto ContactRecordInterface::GetByID(uint32_t id) -> ContactRecord
auto ContactRecordInterface::GetByIdWithTemporary(uint32_t id) -> ContactRecord
{
- LOG_DEBUG("looking contact %" PRIu32 " with tmp", id);
+ debug_db_data("looking contact %" PRIu32 " with tmp", id);
auto contact = contactDB->contacts.getByIdWithTemporary(id);
return getByIdCommon(contact);
}
@@ 495,7 495,7 @@ auto ContactRecordInterface::GetByIdWithTemporary(uint32_t id) -> ContactRecord
auto ContactRecordInterface::getByIdCommon(ContactsTableRow &contact) -> ContactRecord
{
ContactRecord rec = ContactRecord();
- LOG_DEBUG("%" PRIu32, contact.ID);
+ debug_db_data("%" PRIu32, contact.ID);
if (!contact.isValid()) {
return rec;
}
@@ 509,7 509,7 @@ auto ContactRecordInterface::getByIdCommon(ContactsTableRow &contact) -> Contact
rec.numbers = numbers;
}
else {
- LOG_DEBUG("Contact record does not contain any numbers.");
+ debug_db_data("Contact record does not contain any numbers.");
}
auto ring = contactDB->ringtones.getById(contact.ringID);
@@ 517,7 517,7 @@ auto ContactRecordInterface::getByIdCommon(ContactsTableRow &contact) -> Contact
rec.assetPath = ring.assetPath;
}
else {
- LOG_DEBUG("no ring record");
+ debug_db_data("no ring record");
}
auto address = contactDB->address.getById(contact.addressID);
@@ 527,7 527,7 @@ auto ContactRecordInterface::getByIdCommon(ContactsTableRow &contact) -> Contact
rec.mail = address.mail;
}
else {
- LOG_DEBUG("no addres record");
+ debug_db_data("no addres record");
}
auto name = contactDB->name.getById(contact.nameID);
@@ 536,7 536,7 @@ auto ContactRecordInterface::getByIdCommon(ContactsTableRow &contact) -> Contact
rec.alternativeName = name.nameAlternative;
}
else {
- LOG_DEBUG("no name record");
+ debug_db_data("no name record");
}
return rec;
@@ 573,7 573,7 @@ auto ContactRecordInterface::GetLimitOffset(uint32_t offset, uint32_t limit)
auto nrs = getNumbers(contact.numbersID);
if (nrs.size() == 0) {
- LOG_DEBUG("Contact record does not contain any numbers.");
+ debug_db_data("Contact record does not contain any numbers.");
}
auto ring = contactDB->ringtones.getById(contact.ringID);
@@ 621,7 621,7 @@ auto ContactRecordInterface::GetLimitOffsetByField(uint32_t offset,
auto nrs = getNumbers(contact.numbersID);
if (nrs.size() == 0) {
- LOG_DEBUG("Contact record does not contain any numbers.");
+ debug_db_data("Contact record does not contain any numbers.");
}
auto ring = contactDB->ringtones.getById(contact.ringID);
@@ 659,7 659,7 @@ auto ContactRecordInterface::GetLimitOffsetByField(uint32_t offset,
auto nrs = getNumbers(contact.numbersID);
if (nrs.size() == 0) {
- LOG_DEBUG("Contact record does not contain any numbers.");
+ debug_db_data("Contact record does not contain any numbers.");
}
auto name = contactDB->name.getById(contact.nameID);
@@ 704,7 704,7 @@ auto ContactRecordInterface::GetLimitOffsetByField(uint32_t offset,
auto nrs = getNumbers(contact.numbersID);
if (nrs.size() == 0) {
- LOG_DEBUG("Contact record does not contain any numbers.");
+ debug_db_data("Contact record does not contain any numbers.");
}
auto name = contactDB->name.getById(contact.nameID);
@@ 754,7 754,7 @@ auto ContactRecordInterface::GetLimitOffsetByField(uint32_t offset,
auto nrs = getNumbers(contact.numbersID);
if (nrs.size() == 0) {
- LOG_DEBUG("Contact record does not contain any numbers.");
+ debug_db_data("Contact record does not contain any numbers.");
}
auto ring = contactDB->ringtones.getById(contact.ringID);
@@ 804,7 804,7 @@ auto ContactRecordInterface::GetByName(UTF8 primaryName, UTF8 alternativeName)
auto nrs = getNumbers(contact.numbersID);
if (nrs.size() == 0) {
- LOG_DEBUG("Contact record does not contain any numbers.");
+ debug_db_data("Contact record does not contain any numbers.");
}
auto ring = contactDB->ringtones.getById(contact.ringID);
@@ 847,7 847,7 @@ auto ContactRecordInterface::Search(const char *primaryName, const char *alterna
auto nrs = getNumbers(contact.numbersID);
if (nrs.size() == 0) {
- LOG_DEBUG("Contact record does not contain any numbers.");
+ debug_db_data("Contact record does not contain any numbers.");
}
auto ring = contactDB->ringtones.getById(contact.ringID);
@@ 898,13 898,13 @@ auto ContactRecordInterface::GetByNumber(const utils::PhoneNumber::View &numberV
// Contact not found, create temporary one
if (createTempContact == CreateTempContact::True) {
- LOG_INFO("Cannot find contact for number %s, creating temporary one", number.c_str());
+ debug_db_data("Cannot find contact for number %s, creating temporary one", number.c_str());
ContactRecord tmpRecord = {
{.ID = DB_ID_NONE},
.numbers = std::vector<ContactRecord::Number>{ContactRecord::Number(numberView)},
};
if (!Add(tmpRecord)) {
- LOG_ERROR("Cannot add contact record");
+ error_db_data("Cannot add contact record");
return ret;
}
@@ 924,7 924,7 @@ auto ContactRecordInterface::buildNumberMatcher(std::vector<ContactNumberHolder>
contactNumberHolders.emplace_back(number);
}
catch (const utils::PhoneNumber::Error &e) {
- LOG_WARN(
+ debug_db_data(
"Skipping invalid phone number pair: (%s, %s)", number.numberUser.c_str(), number.numbere164.c_str());
continue;
}
@@ 949,13 949,13 @@ auto ContactRecordInterface::MatchByNumber(const utils::PhoneNumber::View &numbe
return std::nullopt;
}
- LOG_INFO("Cannot find contact for number %s, creating temporary one", numberView.getEntered().c_str());
+ debug_db_data("Cannot find contact for number %s, creating temporary one", numberView.getEntered().c_str());
ContactRecord newContact = {{.ID = DB_ID_NONE},
.numbers = std::vector<ContactRecord::Number>{ContactRecord::Number(numberView)},
.groups = {contactDB->groups.getById(ContactsDB::temporaryGroupId())}};
if (!Add(newContact)) {
- LOG_FATAL("Cannot add contact record");
+ error_db_data("Cannot add contact record");
return std::nullopt;
}
@@ 986,7 986,7 @@ auto ContactRecordInterface::getNumbers(const std::string &numbers_id) -> std::v
nr_val = std::stol(nr_str);
}
catch (const std::exception &e) {
- LOG_ERROR("Convertion error from %s, taking default value %ld", nr_str.c_str(), nr_val);
+ error_db_data("Convertion error from %s, taking default value %ld", nr_str.c_str(), nr_val);
}
auto nr = contactDB->number.getById(nr_val);
@@ 999,10 999,11 @@ auto ContactRecordInterface::getNumbers(const std::string &numbers_id) -> std::v
nrs.emplace_back(number.getView(), nr.type);
}
catch (const utils::PhoneNumber::Error &e) {
- LOG_ERROR("Invalid contact's number pair: \"%s\" (\"%s\", \"%s\"). Using user number instead of a pair.",
- e.what(),
- nr.numberUser.c_str(),
- nr.numbere164.c_str());
+ error_db_data(
+ "Invalid contact's number pair: \"%s\" (\"%s\", \"%s\"). Using user number instead of a pair.",
+ e.what(),
+ nr.numberUser.c_str(),
+ nr.numbere164.c_str());
nrs.emplace_back(utils::PhoneNumber(nr.numberUser, utils::country::Id::UNKNOWN).getView(), nr.type);
}
}
M module-db/Interface/ContactRecord.hpp => module-db/Interface/ContactRecord.hpp +4 -3
@@ 5,6 5,7 @@
#include <Databases/ContactsDB.hpp>
#include <Common/Query.hpp>
+#include <Common/Logging.hpp>
#include <Tables/ContactsGroups.hpp>
#include <i18n/i18n.hpp>
@@ 23,8 24,8 @@
struct ContactRecord : public Record
{
- UTF8 primaryName = "";
- UTF8 alternativeName = "";
+ UTF8 primaryName = "";
+ UTF8 alternativeName = "";
uint32_t contactPosOnList = 0;
struct Number
@@ 71,7 72,7 @@ struct ContactRecord : public Record
inline auto getFormattedName(const NameFormatType type = NameFormatType::Default) const -> UTF8
{
if (isTemporary()) {
- LOG_DEBUG("temporary contact, number as name: '%s'", getNumberAsName().c_str());
+ debug_db_data("temporary contact, number as name: '%s'", getNumberAsName().c_str());
return getNumberAsName();
}
if (primaryName.length() > 0) {
M module-db/Tables/CalllogTable.cpp => module-db/Tables/CalllogTable.cpp +0 -1
@@ 197,7 197,6 @@ uint32_t CalllogTable::count(EntryState state)
break;
}
query += ";";
- LOG_DEBUG("> %s", query.c_str());
auto queryRet = db->query(query.c_str());
if (queryRet == nullptr || queryRet->getRowCount() == 0) {
M module-db/Tables/ContactsTable.cpp => module-db/Tables/ContactsTable.cpp +7 -7
@@ 79,20 79,20 @@ ContactsTableRow ContactsTable::getById(uint32_t id)
ContactsTableRow ContactsTable::getByIdWithTemporary(uint32_t id)
{
- LOG_DEBUG("%s", __FUNCTION__);
+ debug_db_data("%s", __FUNCTION__);
auto retQuery = db->query(statements::selectWithTemp, id);
return getByIdCommon(std::move(retQuery));
}
ContactsTableRow ContactsTable::getByIdCommon(std::unique_ptr<QueryResult> retQuery)
{
- LOG_DEBUG("%s", __FUNCTION__);
+ debug_db_data("%s", __FUNCTION__);
if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
LOG_DEBUG("no results");
return ContactsTableRow();
}
- LOG_DEBUG(
+ debug_db_data(
"got results: %" PRIu32 "; ID: %" PRIu32, retQuery->getRowCount(), (*retQuery)[ColumnName::id].getInt32());
return ContactsTableRow{
{.ID = (*retQuery)[ColumnName::id].getUInt32()},
@@ 133,7 133,7 @@ std::vector<ContactsTableRow> ContactsTable::Search(const std::string &primaryNa
if (!number.empty())
q += "t3.number_e164 like '%%" + number + "%%'";
- LOG_DEBUG("query: \"%s\"", q.c_str());
+ debug_db_data("query: \"%s\"", q.c_str());
auto retQuery = db->query(q.c_str());
if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
@@ 190,7 190,7 @@ std::vector<std::uint32_t> ContactsTable::GetIDsSortedByName(std::uint32_t limit
std::vector<std::uint32_t> ids_limit;
std::string query = GetSortedByNameQueryString(ContactQuerySection::Favourites);
- LOG_DEBUG("query: %s", query.c_str());
+ debug_db_data("query: %s", query.c_str());
auto queryRet = db->query(query.c_str());
if (queryRet == nullptr) {
@@ 202,7 202,7 @@ std::vector<std::uint32_t> ContactsTable::GetIDsSortedByName(std::uint32_t limit
} while (queryRet->nextRow());
query = GetSortedByNameQueryString(ContactQuerySection::Mixed);
- LOG_DEBUG("query: %s", query.c_str());
+ debug_db_data("query: %s", query.c_str());
queryRet = db->query(query.c_str());
if ((queryRet == nullptr) || (queryRet->getRowCount() == 0)) {
return ids;
@@ 323,7 323,7 @@ std::vector<std::uint32_t> ContactsTable::GetIDsSortedByField(
query += " ;";
- LOG_DEBUG("query: %s", query.c_str());
+ debug_db_data("query: %s", query.c_str());
auto queryRet = db->query(query.c_str());
if ((queryRet == nullptr) || (queryRet->getRowCount() == 0)) {
return ids;
M module-db/Tables/ContactsTable.hpp => module-db/Tables/ContactsTable.hpp +1 -0
@@ 4,6 4,7 @@
#pragma once
#include "Common/Common.hpp"
+#include "Common/Logging.hpp"
#include "Record.hpp"
#include "Table.hpp"
#include "utf8/UTF8.hpp"
M module-gui/README.md => module-gui/README.md +29 -25
@@ 44,9 44,9 @@ All of these are asynchronous and there is little state machine maintenance.
3. Applications react on key **releases** actions, in most scenarios key press event is useless
4. all applications, if it hasn't been overriden in `gui::AppWindow` will try to return to previous window or application on `back`
-# `gui::Item` Key Press handling
+## `gui::Item` Key Press handling
-## What happens when you press a key?
+### What happens when you press a key?

@@ 60,7 60,7 @@ All of these are asynchronous and there is little state machine maintenance.
* use widgets which override default key handling (see `gui::Item::onInput`)
* have own `gui::KeyInputMappedTranslation` + `gui::InputMode` and process key press however they want
-## How to handle key press
+### How to handle key press
There are at least 3 ways to handle key press, listed in order of execution:
* `gui::Item::onInput` - if not redefined calls `inputCallback`; if handled here, other calls wont be called
@@ 75,67 75,71 @@ There are 2 set of parameters for key press:
* `gui::KeyCode` - initially parsed key code
* `gui::RawKey` - raw key code, to be processed in widget based on event i.e. translate pressing key `1` 3 times into C in `gui::Text` mode `ABC`
-## How to add key mapping when basic key maps are not enough?
+### How to add key mapping when basic key maps are not enough?
-* Key maps are specific key translation mappings. i.e. press `1` 3 times to get C, press `1` 4 times to get A, etc.
+* Key maps are specific key translation mappings i.e. press `1` 3 times to get C, press `1` 4 times to get A, etc.
* basic key maps are stored in: `InputMode`, right now there are following `InputMode::Mode`s: [`ABC`, `abc`, `digit`, `phone`]
* key maps in `gui::InputMode` are changed in regards of language settings
-### How to add a new key map, i.e. `phone`
+#### How to add a new key map
+
+How to add a new key map, i.e. `phone`:
1. Add new file for your key map: `cp image/assets/profiles/template.kprof image/assets/profiles/phone.kprof`
2. Change your template accordingly
-3. Pin new key map = add it to language support, add: `"common_kbd_phone": "phone"` to at least `image/assets/lang/lang_en.json` if it will differ per language, prepare one `kprof` file per language
+3. Pin new key map (add it to language support) by adding: `"common_kbd_phone": "phone"` to at least `image/assets/lang/lang_en.json` if it will differ per language, prepare one `kprof` file per language
4. Add new key map to `gui::InputMode`
- Add `InputMode::Mode` enum i.e. `InputMode::Mode::phone`
- - Add new Mode to input mode mapping in `InputMode.cpp` (same as with other enums)
- - Test new added mode in: `UITestWindow.cpp`
+ - Add new mode to input mode mapping in `InputMode.cpp` (same as with other enums)
+ - Test newly added mode in: `UITestWindow.cpp`
- Test new key map on phone
5. Load key map to phone
Now you can use `InputMode::Mode::phone` translation in `gui::Text` widget.
This means `gui::Text` will automatically change text on key press for you, same as in modes `InputMode::Mode::phone` etc.
-# Adding new functionalities - Visitor pattern in `gui::Item`
+## Adding new functionalities - visitor pattern in `gui::Item`
The `gui::Item` class is compatible with visitor pattern providing double dispatch behaviour.
-The double dispatch mechanism enables, for all classes in `gui::Item`'s inheritance hierarchy, to easily equip them with new polymorphic behavior without changing classes themselves.
+The double dispatch mechanism for all classes in `gui::Item`'s inheritance hierarchy enables easily equipping them with new polymorphic behavior without changing classes themselves.
-## Structure
+### Structure
-Every new functionality to be added to `gui::Item` hierarchy requires creation of new concrete visitor that publicly inherits from `gui::GuiVisitor` interface and specify respective behavior.
-In order to ensure that a class in `gui::Item` hierarchy is recognized by it's concrete type in `ConcreteVisitor::visit(...)` method, class must override `gui::Item::accept(gui::GuiVisitor &)`,
+Every new functionality to be added to `gui::Item` hierarchy requires creation of new concrete visitor that publicly inherits from `gui::GuiVisitor` interface and specifies respective behavior.
+In order to ensure that a class in `gui::Item` hierarchy is recognized by its concrete type in `ConcreteVisitor::visit(...)` method, class must override `gui::Item::accept(gui::GuiVisitor &)`,
otherwise it will be resolved as a closest ancestor. On the diagram below both `gui::CustomItem1` and `gui::CustomItem2` will be resolved as `gui::Rect`
despite existing `gui::GuiVisitor::visit(gui::CustomItem2 &)` overload and `gui::CustomItem1::accept(...)` override.

-## Tree of `gui::Item`
+### Tree of `gui::Item`
-Each a `gui::Item` object is used as a node to build UI general tree.
-That relation is simplest thought of as a tree of dependencies with a node being a parent of zero, one or more other nodes.
-Concerning need of a `ConcreteVisitors` to visit not only parent but also all it's children,
+Each `gui::Item` object is used as a node to build a UI general tree.
+That relation can simply be thought of as a tree of dependencies with a node being a parent of zero, one or more other nodes.
+Concerning the need of a `ConcreteVisitors` to visit not only the parent but also all its children,
`gui::ItemTree` is an interface class providing abstract interface for implementation of `gui::Item` tree traversal.
The concrete realization of `gui::ItemTree` is `gui::DepthFirstItemTree`.

-### Depth-First tree of `gui::Item`
+#### Depth-First tree of `gui::Item`
`gui::DepthFirstItemTree` builds tree of parent-children relation for any `gui::Item` pointed as the root.
+
The class offers two traverse modes:
-* `PreOrder` - in this mode a parent is precedes all it's children
-* `PostOrder` - in this mode all children precede it's parent
+* `PreOrder` - in this mode a parent precedes all its children
+* `PostOrder` - in this mode all children precede their parent
-## Example
+### Example

-# Domain Object Model of `gui::Item`
+## Domain Object Model of `gui::Item`
-Each `gui::Item` object can be serialized into json-formatted stream using `gui::Item2JsonSerializer`.
+Each `gui::Item` object can be serialized into JSON-formatted stream using `gui::Item2JsonSerializer`.
The serializing class employs dedicated `gui::Item2JsonSerializingVisitor`, `gui::DepthFirstItemTree` in `PostOrder` mode
-in a sequence flow analogous to the one presented above. Below exemplary fragment of DOM serialization output is presented.
+in a sequence flow analogous to the one presented above. Please find an exemplary fragment of DOM serialization output below.
+
```asm
{"Rect": {
"Active": true,
M module-gui/gui/Common.hpp => module-gui/gui/Common.hpp +6 -3
@@ 30,13 30,16 @@ namespace gui
struct Point
{
Position x = 0, y = 0;
- Point(Position x = 0, Position y = 0) : x(x), y(y)
+
+ constexpr Point(Position x = 0, Position y = 0) : x(x), y(y)
{}
- [[nodiscard]] auto get(Axis axis) const -> Length
+
+ [[nodiscard]] constexpr auto get(Axis axis) -> Length
{
return Axis::X == axis ? x : y;
}
- [[nodiscard]] auto isZero() const -> bool
+
+ [[nodiscard]] constexpr auto isZero() -> bool
{
return 0 == x && 0 == y;
}
M module-gui/gui/core/Context.hpp => module-gui/gui/core/Context.hpp +13 -9
@@ 51,26 51,30 @@ namespace gui
/**
* @brief returns pointer to context's data;
*/
+ inline const uint8_t *getData() const
+ {
+ return data;
+ }
inline uint8_t *getData()
{
return data;
- };
- inline int16_t getX()
+ }
+ inline int16_t getX() const
{
return x;
- };
- inline int16_t getY()
+ }
+ inline int16_t getY() const
{
return y;
- };
- inline uint16_t getW()
+ }
+ inline uint16_t getW() const
{
return w;
- };
- inline uint16_t getH()
+ }
+ inline uint16_t getH() const
{
return h;
- };
+ }
inline bool addressInData(const uint8_t *ptr) const
{
M module-gui/gui/core/RawFont.cpp => module-gui/gui/core/RawFont.cpp +1 -1
@@ 306,7 306,7 @@ namespace gui
commandRect->penWidth = unsupported->xoffset;
auto renderCtx = std::make_unique<Context>(unsupported->width, unsupported->height);
- std::list<Command> commands;
+ std::list<std::unique_ptr<gui::DrawCommand>> commands;
commands.emplace_back(std::move(commandRect));
Renderer().render(renderCtx.get(), commands);
M module-gui/gui/core/Renderer.cpp => module-gui/gui/core/Renderer.cpp +1 -1
@@ 288,7 288,7 @@ namespace gui
delete drawCtx;
}
- void Renderer::render(Context *ctx, std::list<Command> &commands)
+ void Renderer::render(Context *ctx, std::list<std::unique_ptr<DrawCommand>> &commands)
{
if (ctx == nullptr) {
return;
M module-gui/gui/core/Renderer.hpp => module-gui/gui/core/Renderer.hpp +1 -1
@@ 50,7 50,7 @@ namespace gui
public:
virtual ~Renderer() = default;
- void render(Context *ctx, std::list<Command> &commands);
+ void render(Context *ctx, std::list<std::unique_ptr<DrawCommand>> &commands);
};
} /* namespace gui */
M module-gui/gui/input/Profile.cpp => module-gui/gui/input/Profile.cpp +26 -215
@@ 1,249 1,60 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-#include <string>
-#include <iomanip>
-#include <algorithm>
-#include <sstream>
#include "log/log.hpp"
#include "utf8/UTF8.hpp"
#include "Profile.hpp"
#include <Utils.hpp>
+#include <gsl>
namespace gui
{
- const uint32_t KeyProfile::none_key = 0;
+ Profile::Profile(const std::string &filepath) : name(filepath), inputChars(createJson(filepath))
+ {}
- Profile::Profile(const std::string &name)
- {
- LOG_INFO("Create!");
- load(name);
- }
-
- Profile::Profile(Profile &&p)
- {
- this->name = p.name;
- this->keys = p.keys;
- // this is important, we need to assure that moved Profile doesn't clean up our memory again
- p.clear();
- }
-
- Profile::~Profile()
- {
- for (auto it = keys.begin(); it != keys.end(); it++) {
- delete it->second;
- }
- keys.clear();
- }
-
- static inline std::string trim(const std::string &s)
- {
- auto wsfront = std::find_if_not(s.begin(), s.end(), [](int c) { return std::isspace(c); });
- return std::string(wsfront,
- std::find_if_not(s.rbegin(), std::string::const_reverse_iterator(wsfront), [](int c) {
- return std::isspace(c);
- }).base());
- }
-
- template <typename Out> void split(const std::string &s, char delim, Out result)
- {
- std::stringstream ss(s);
- std::string item;
- while (std::getline(ss, item, delim)) {
- *(result++) = item;
- }
- }
-
- static std::vector<std::string> split(const std::string &s, char delim)
- {
- std::vector<std::string> elems;
- split(s, delim, std::back_inserter(elems));
- return elems;
- }
-
- void Profile::clear()
- {
- keys.clear();
- }
-
- std::string Profile::getName() const noexcept
+ const std::string &Profile::getName() noexcept
{
return name;
}
- bool Profile::load(const std::string &filename)
+ const json11::Json Profile::createJson(const std::string &filepath)
{
- auto file = std::fopen(filename.c_str(), "rb");
+ auto fd = std::fopen(filepath.c_str(), "r");
- if (file == nullptr) {
- LOG_FATAL("no KeyProfile file: %s", filename.c_str());
- return false;
+ if (fd == nullptr) {
+ LOG_FATAL("Error during opening file %s", filepath.c_str());
+ return json11::Json();
}
- enum class LineType
- {
- KEY_CODE = 0,
- CYCLIC,
- CHARACTERS,
- TIMEOUTS
- };
+ uint32_t fsize = utils::filesystem::filelength(fd);
- LineType lineType = LineType::KEY_CODE;
- bool name = false;
+ auto stream = std::make_unique<char[]>(fsize + 1);
- KeyProfile *pk = nullptr;
- KeyProfile tmp;
- while (std::feof(file) != true) {
- const auto line = trim(utils::filesystem::getline(file));
- if ((line[0] == '#') || (line.empty())) {
- continue;
- }
- else {
+ memset(stream.get(), 0, fsize + 1);
- // first not commented line is a name of profile
- if (name == false) {
- LOG_INFO("profile name: %s", line.c_str());
- setName(line);
- name = true;
- }
- else {
- // new structure, create profile key and read the keyboard's key code
- if (lineType == LineType::KEY_CODE) {
- uint32_t keyCode;
- std::stringstream(line) >> keyCode;
- pk = new KeyProfile();
- pk->keyCode = keyCode;
- lineType = LineType::CYCLIC;
- }
- else if (lineType == LineType::CYCLIC) {
- pk->cyclic = (line.compare("true") == 0);
- lineType = LineType::CHARACTERS;
- }
- else if (lineType == LineType::CHARACTERS) {
- addCharacters(pk, line);
- lineType = LineType::TIMEOUTS;
- }
- else if (lineType == LineType::TIMEOUTS) {
- addTimeouts(pk, line);
- if ((pk->chars.size() == pk->timeouts.size()) && (keys.find(pk->keyCode) == keys.end())) {
- addKeyProfile(pk);
- }
- else {
- LOG_FATAL("Incorrect number of chars or key code duplicate key code: [%" PRIu32 "]",
- pk->keyCode);
- delete pk;
- pk = nullptr;
- }
+ std::fread(stream.get(), 1, fsize, fd);
- lineType = LineType::KEY_CODE;
- }
- else {
- LOG_FATAL("invalid line: [%s]", line.c_str());
- }
- }
- }
- }
+ std::string err;
+ json11::Json parsedJson = json11::Json::parse(stream.get(), err);
- std::fclose(file);
+ auto _ = gsl::finally([fd] { std::fclose(fd); });
- return true;
- }
-
- int is_utf8_character(unsigned char c)
- {
- if ((c >> 7) == 0b1) {
- if ((c >> 6) == 0b10) {
- return 2; // 2nd, 3rd or 4th byte of a utf-8 character
- }
- else {
- return 1; // 1st byte of a utf-8 character
- }
- }
- else {
- return 0; // a single byte character
- }
- }
-
- static std::vector<std::string> split_str(const std::string &s, int chars_number, int start_pos)
- {
- int pos = start_pos, char_pos, bytes_to_write;
- std::vector<std::string> elems;
- while (pos < (int)s.length()) {
- bytes_to_write = chars_number;
- char_pos = pos + 1;
- if (is_utf8_character(s[char_pos]) > 0) {
- bytes_to_write = bytes_to_write + 1;
- }
- elems.push_back(s.substr(pos, bytes_to_write));
- pos = pos + bytes_to_write + 1;
- }
- return elems;
- }
-
- void Profile::addCharacters(KeyProfile *pk, const std::string &s) const
- {
-
- uint32_t charKey;
- std::vector<std::string> vec = split_str(s, 3, 0);
- for (std::string s : vec) {
- std::string ts = trim(s);
- if (ts[0] == '\'') {
- // empty character - no character
- if (s.size() == 2) {
- pk->chars.push_back(0);
- break;
- }
- ts = s.substr(1, s.size() - 1);
- UTF8 utf = UTF8(ts);
- charKey = utf[0];
- pk->chars.push_back(charKey);
- }
- else if (ts.substr(0, 2) == "0x") {
- std::stringstream ss;
- ss << std::hex << ts;
- ss >> charKey;
- pk->chars.push_back(charKey);
- }
+ if (err.length() != 0) {
+ LOG_FATAL("%s", err.c_str());
+ return json11::Json();
}
- }
- void Profile::addTimeouts(KeyProfile *pk, const std::string &s) const
- {
- uint32_t timeout;
- std::vector<std::string> vec = split(s, ',');
- for (std::string s : vec) {
- std::stringstream(trim(s)) >> timeout;
- pk->timeouts.push_back(timeout);
- }
- }
-
- void Profile::addKeyProfile(KeyProfile *pk)
- {
- if (pk != nullptr)
- keys.insert(std::pair<uint32_t, KeyProfile *>(pk->keyCode, pk));
- }
-
- void Profile::setName(const std::string &name)
- {
- this->name = name;
- }
-
- const KeyProfile *Profile::getKeyProfile(uint32_t keyCode) const
- {
- auto key = keys.find(keyCode);
- if (key != keys.end())
- return key->second;
- return nullptr;
+ return parsedJson;
}
- uint32_t Profile::get(bsp::KeyCodes code, uint32_t times)
+ uint32_t Profile::getCharKey(bsp::KeyCodes code, uint32_t times)
{
- const KeyProfile *p = getKeyProfile(static_cast<uint32_t>(code));
- if (p == nullptr) {
- LOG_DEBUG("KeyProfile for key: %" PRIu32 " not found", static_cast<uint32_t>(code));
- return 0;
+ std::string ts = inputChars[utils::to_string(static_cast<int>(code))].string_value();
+ UTF8 utf = UTF8(ts);
+ if (ts.size() > 0) {
+ return utf[times % utf.length()];
}
- return p->chars[times % p->chars.size()];
+ return utf[0];
}
} /* namespace gui */
M module-gui/gui/input/Profile.hpp => module-gui/gui/input/Profile.hpp +10 -30
@@ 6,46 6,26 @@
#include <cstdint>
#include <vector>
#include <map>
+#include "json/json11.hpp"
+
namespace gui
{
- class KeyProfile
- {
- public:
- static const uint32_t none_key; /// defaults to 0
- uint32_t keyCode = none_key;
- bool cyclic = false;
- std::vector<uint32_t> chars;
- std::vector<uint32_t> timeouts;
-
- virtual ~KeyProfile() = default;
-
- void addCharacters(const std::string &s);
- void addTimeouts(const std::string &s);
- };
-
class Profile
{
+ private:
std::string name;
- std::map<uint32_t, KeyProfile *> keys = {};
+ json11::Json inputChars;
- void addCharacters(KeyProfile *pk, const std::string &s) const;
- void addTimeouts(KeyProfile *pk, const std::string &s) const;
- void addKeyProfile(KeyProfile *pk);
- void setName(const std::string &name);
- const KeyProfile *getKeyProfile(uint32_t keyCode) const;
+ const json11::Json createJson(const std::string &filepath);
public:
- void clear();
- Profile() = default;
- Profile(const std::string &name);
- Profile(Profile &&p);
- virtual ~Profile();
-
- [[nodiscard]] std::string getName() const noexcept;
- bool load(const std::string &filename);
+ static constexpr uint32_t none_key = 0;
+ Profile() = default;
+ explicit Profile(const std::string &filepath);
- uint32_t get(bsp::KeyCodes code, uint32_t times);
+ [[nodiscard]] const std::string &getName() noexcept;
+ [[nodiscard]] uint32_t getCharKey(bsp::KeyCodes code, uint32_t times);
};
} /* namespace gui */
M module-gui/gui/input/Translator.cpp => module-gui/gui/input/Translator.cpp +31 -18
@@ 9,6 9,11 @@
namespace gui
{
+ namespace
+ {
+ constexpr auto profilesFolder = "assets/profiles";
+ constexpr auto extension = ".json";
+ } // namespace
void recon_long_press(InputEvent &evt, const RawKey &key, const RawKey &prev_key_press, uint32_t time)
{
@@ 180,26 185,30 @@ namespace gui
if (key.state == RawKey::State::Released) {
prev_key_press = key;
}
- return Profiles::get(keymap).get(key.key_code, times);
+ return profiles.get(keymap).getCharKey(key.key_code, times);
+ }
+
+ uint32_t KeyInputMappedTranslation::getTimes() const noexcept
+ {
+ return times;
}
void Profiles::loadProfile(const std::string &filepath)
{
LOG_INFO("Load profile: %s", filepath.c_str());
auto p = Profile(filepath);
- if (p.getName() != std::string()) {
- profiles.insert({p.getName(), std::move(p)});
+ if (auto name = p.getName(); !name.empty()) {
+ profilesList.insert({p.getName(), std::move(p)});
}
}
- std::vector<std::string> Profiles::getProfilesList(std::string ext)
+
+ std::vector<std::string> Profiles::getProfilesPaths()
{
std::vector<std::string> profileFiles;
- LOG_INFO("Scanning %s profiles folder: %s", ext.c_str(), profilesFolder);
+ LOG_INFO("Scanning %s profiles folder: %s", extension, profilesFolder);
for (const auto &entry : std::filesystem::directory_iterator(profilesFolder)) {
- if (!std::filesystem::is_directory(entry) && entry.path().extension().string() == ext) {
- profileFiles.push_back(entry.path().string());
- }
+ profileFiles.push_back(std::filesystem::path(entry.path()));
}
LOG_INFO("Total number of profiles: %u", static_cast<unsigned int>(profileFiles.size()));
@@ 208,16 217,17 @@ namespace gui
void Profiles::init()
{
- std::vector<std::string> profileFiles = getProfilesList(".kprof");
- for (std::string mapName : profileFiles) {
- if (std::size(mapName)) {
- loadProfile(mapName);
+ std::vector<std::string> profileFilesPaths = getProfilesPaths();
+ for (std::string filePath : profileFilesPaths) {
+ if (std::size(filePath)) {
+ loadProfile(filePath);
}
}
- if (std::size(profiles) == 0) {
+ if (std::size(profilesList) == 0) {
LOG_ERROR("No keyboard profiles loaded");
}
}
+
Profiles &Profiles::get()
{
static Profiles *p;
@@ 227,16 237,19 @@ namespace gui
}
return *p;
}
+
Profile &Profiles::get(const std::string &name)
{
+ auto filepath = std::string(profilesFolder) + "/" + name + extension;
// if profile not in profile map -> load
- if (std::size(name) == 0) {
- LOG_ERROR("Request for non existend profile: %s", name.c_str());
+ if (std::size(filepath) == 0) {
+ LOG_ERROR("Request for nonexistent profile: %s", filepath.c_str());
return get().empty;
}
- if (get().profiles.find(name) == get().profiles.end()) {
- get().loadProfile(name);
+ if (get().profilesList.find(filepath) == get().profilesList.end()) {
+ get().loadProfile(filepath);
}
- return get().profiles[name];
+ return get().profilesList[filepath];
}
+
} /* namespace gui */
M module-gui/gui/input/Translator.hpp => module-gui/gui/input/Translator.hpp +17 -20
@@ 50,35 50,32 @@ namespace gui
InputEvent translate(uint32_t timeout);
};
- /// translator using & switching KeyMaps for use per widget basis ,called for selected widget, per widget basis
- class KeyInputMappedTranslation : public KeyBaseTranslation
- {
- uint32_t times = 0;
-
- public:
- bool setProfile(std::string profileName);
- uint32_t handle(RawKey key, const std::string &keymap);
- uint32_t getTimes()
- {
- return times;
- }
- };
-
/// profiles cache - load once for all
class Profiles
{
private:
- const char *profilesFolder = "assets/profiles";
- std::map<std::string, gui::Profile> profiles = {};
- Profile empty;
+ std::map<std::string, gui::Profile> profilesList = {};
void loadProfile(const std::string &filepath);
- std::vector<std::string> getProfilesList(std::string ext);
+ std::vector<std::string> getProfilesPaths();
void init();
+ Profile empty;
- public:
static Profiles &get();
- static Profile &get(const std::string &name);
+
+ public:
+ Profile &get(const std::string &name);
+ };
+
+ /// translator using & switching KeyMaps for use per widget basis ,called for selected widget, per widget basis
+ class KeyInputMappedTranslation : public KeyBaseTranslation
+ {
+ uint32_t times = 0;
+ Profiles profiles;
+
+ public:
+ uint32_t handle(RawKey key, const std::string &keymap);
+ uint32_t getTimes() const noexcept;
};
} /* namespace gui */
M module-gui/gui/widgets/Text.cpp => module-gui/gui/widgets/Text.cpp +2 -2
@@ 165,7 165,7 @@ namespace gui
{
auto tmp_document = text::RichTextParser().parse(text, &format);
if (tmp_document->isEmpty()) {
- LOG_ERROR("Nothing to parse/parser error in rich text: %s", text.c_str());
+ debug_text("Nothing to parse/parser error in rich text: %s", text.c_str());
addText(text); // fallback
}
for (auto block : tmp_document->getBlockCursor(0)) {
@@ 545,7 545,7 @@ namespace gui
auto code = translator.handle(inputEvent.key, mode ? mode->get() : "");
- if (code != KeyProfile::none_key && checkAdditionBounds(code) == AdditionBound::CanAddAll) {
+ if (code != Profile::none_key && checkAdditionBounds(code) == AdditionBound::CanAddAll) {
setCursorStartPosition(CursorStartPosition::Offset);
M module-gui/test/test-catch/CMakeLists.txt => module-gui/test/test-catch/CMakeLists.txt +1 -0
@@ 9,6 9,7 @@ add_catch2_executable(
test-gui-resizes.cpp
../mock/TestWindow.cpp
../mock/InitializedFontManager.cpp
+ test-language-input-parser.cpp
INCLUDE
..
LIBS
A module-gui/test/test-catch/test-language-input-parser.cpp => module-gui/test/test-catch/test-language-input-parser.cpp +103 -0
@@ 0,0 1,103 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <catch2/catch.hpp>
+#include <Translator.hpp>
+
+TEST_CASE("Parsing English input language")
+{
+
+ gui::KeyInputMappedTranslation translator;
+ RawKey key;
+
+ SECTION("Getting charKey from lower letters")
+ {
+ key.key_code = bsp::KeyCodes::NumericKey1;
+ REQUIRE(translator.handle(key, "English_lower") == 46);
+ key.key_code = bsp::KeyCodes::NumericKey2;
+ REQUIRE(translator.handle(key, "English_lower") == 97);
+ key.key_code = bsp::KeyCodes::NumericKey3;
+ REQUIRE(translator.handle(key, "English_lower") == 100);
+ key.key_code = bsp::KeyCodes::NumericKey4;
+ REQUIRE(translator.handle(key, "English_lower") == 103);
+ key.key_code = bsp::KeyCodes::NumericKey5;
+ REQUIRE(translator.handle(key, "English_lower") == 106);
+ key.key_code = bsp::KeyCodes::NumericKey6;
+ REQUIRE(translator.handle(key, "English_lower") == 109);
+ key.key_code = bsp::KeyCodes::NumericKey7;
+ REQUIRE(translator.handle(key, "English_lower") == 112);
+ key.key_code = bsp::KeyCodes::NumericKey8;
+ REQUIRE(translator.handle(key, "English_lower") == 116);
+ key.key_code = bsp::KeyCodes::NumericKey9;
+ REQUIRE(translator.handle(key, "English_lower") == 119);
+ key.key_code = bsp::KeyCodes::NumericKey0;
+ REQUIRE(translator.handle(key, "English_lower") == 32);
+ }
+
+ SECTION("Getting charKey from upper letters")
+ {
+ key.key_code = bsp::KeyCodes::NumericKey1;
+ REQUIRE(translator.handle(key, "English_upper") == 46);
+ key.key_code = bsp::KeyCodes::NumericKey2;
+ REQUIRE(translator.handle(key, "English_upper") == 65);
+ key.key_code = bsp::KeyCodes::NumericKey3;
+ REQUIRE(translator.handle(key, "English_upper") == 68);
+ key.key_code = bsp::KeyCodes::NumericKey4;
+ REQUIRE(translator.handle(key, "English_upper") == 71);
+ key.key_code = bsp::KeyCodes::NumericKey5;
+ REQUIRE(translator.handle(key, "English_upper") == 74);
+ key.key_code = bsp::KeyCodes::NumericKey6;
+ REQUIRE(translator.handle(key, "English_upper") == 77);
+ key.key_code = bsp::KeyCodes::NumericKey7;
+ REQUIRE(translator.handle(key, "English_upper") == 80);
+ key.key_code = bsp::KeyCodes::NumericKey8;
+ REQUIRE(translator.handle(key, "English_upper") == 84);
+ key.key_code = bsp::KeyCodes::NumericKey9;
+ REQUIRE(translator.handle(key, "English_upper") == 87);
+ key.key_code = bsp::KeyCodes::NumericKey0;
+ REQUIRE(translator.handle(key, "English_upper") == 32);
+ }
+}
+
+TEST_CASE("Parsing numeric keyboard")
+{
+ gui::KeyInputMappedTranslation translator;
+ RawKey key;
+
+ key.key_code = bsp::KeyCodes::NumericKey1;
+ REQUIRE(translator.handle(key, "numeric") == 49);
+ key.key_code = bsp::KeyCodes::NumericKey2;
+ REQUIRE(translator.handle(key, "numeric") == 50);
+ key.key_code = bsp::KeyCodes::NumericKey3;
+ REQUIRE(translator.handle(key, "numeric") == 51);
+ key.key_code = bsp::KeyCodes::NumericKey4;
+ REQUIRE(translator.handle(key, "numeric") == 52);
+ key.key_code = bsp::KeyCodes::NumericKey5;
+ REQUIRE(translator.handle(key, "numeric") == 53);
+ key.key_code = bsp::KeyCodes::NumericKey6;
+ REQUIRE(translator.handle(key, "numeric") == 54);
+ key.key_code = bsp::KeyCodes::NumericKey7;
+ REQUIRE(translator.handle(key, "numeric") == 55);
+ key.key_code = bsp::KeyCodes::NumericKey8;
+ REQUIRE(translator.handle(key, "numeric") == 56);
+ key.key_code = bsp::KeyCodes::NumericKey9;
+ REQUIRE(translator.handle(key, "numeric") == 57);
+ key.key_code = bsp::KeyCodes::NumericKey0;
+ REQUIRE(translator.handle(key, "numeric") == 48);
+}
+
+TEST_CASE("Getting charKey after clicking button twice")
+{
+ gui::KeyInputMappedTranslation translator;
+ gui::KeyBaseTranslation baseTranslation;
+ RawKey key;
+
+ key.key_code = bsp::KeyCodes::NumericKey2;
+ key.state = RawKey::State::Released;
+ baseTranslation.prev_key_press = key;
+ translator.handle(key, "English_lower");
+ REQUIRE(translator.handle(key, "English_lower") == 98);
+}
M module-os/RTOSWrapper/cqueue.cpp => module-os/RTOSWrapper/cqueue.cpp +8 -0
@@ 62,6 62,14 @@ Queue::~Queue()
vQueueDelete(handle);
}
+bool Queue::Overwrite(void *item)
+{
+ BaseType_t success;
+
+ success = xQueueOverwrite(handle, item);
+
+ return success == pdTRUE ? true : false;
+}
bool Queue::Enqueue(void *item)
{
M module-os/RTOSWrapper/include/queue.hpp => module-os/RTOSWrapper/include/queue.hpp +8 -0
@@ 136,6 136,14 @@ class Queue {
virtual ~Queue();
/**
+ * Add an item to the queue, or overwrites the existing one.
+ * Works only for queues 1-element long.
+ * @param item The item to be added.
+ * @return true if the item was added, false otherwise.
+ */
+ bool Overwrite(void *item);
+
+ /**
* Add an item to the back of the queue.
*
* @param item The item you are adding.
M module-services/service-appmgr/model/ApplicationManager.cpp => module-services/service-appmgr/model/ApplicationManager.cpp +3 -6
@@ 6,7 6,6 @@
#include <module-apps/messages/AppMessage.hpp>
#include <Common.hpp>
-#include <Common/Common.hpp>
#include <Service/Bus.hpp>
#include <Service/Message.hpp>
#include <Service/Timer.hpp>
@@ 16,10 15,8 @@
#include <i18n/i18n.hpp>
#include <log/log.hpp>
#include <service-appmgr/messages/Message.hpp>
-#include <service-db/DBServiceAPI.hpp>
#include <service-evtmgr/EventManager.hpp>
#include <service-gui/ServiceGUI.hpp>
-#include <service-eink/Common.hpp>
#include <service-eink/ServiceEink.hpp>
#include <service-gui/Common.hpp>
@@ 135,12 132,12 @@ namespace app::manager
void ApplicationManager::startSystemServices()
{
if (bool ret = sys::SystemManager::CreateService(
- std::make_shared<sgui::ServiceGUI>(service::name::gui, GetName(), 480, 600), this);
+ std::make_shared<service::gui::ServiceGUI>(service::name::gui, GetName()), this);
!ret) {
LOG_ERROR("Failed to initialize GUI service");
}
- if (bool ret =
- sys::SystemManager::CreateService(std::make_shared<ServiceEink>(service::name::eink, GetName()), this);
+ if (bool ret = sys::SystemManager::CreateService(
+ std::make_shared<service::eink::ServiceEink>(service::name::eink, GetName()), this);
!ret) {
LOG_ERROR("Failed to initialize EInk service");
}
M module-services/service-audio/ServiceAudio.cpp => module-services/service-audio/ServiceAudio.cpp +0 -4
@@ 474,7 474,6 @@ sys::MessagePointer ServiceAudio::DataReceivedHandler(sys::DataMessage *msgl, sy
bool isBusy = IsBusy();
auto &msgType = typeid(*msgl);
- LOG_DEBUG("msgType %d %s", static_cast<int>(msgl->messageType), msgType.name());
if (msgType == typeid(AudioNotificationMessage)) {
auto *msg = static_cast<AudioNotificationMessage *>(msgl);
@@ 526,9 525,6 @@ sys::MessagePointer ServiceAudio::DataReceivedHandler(sys::DataMessage *msgl, sy
auto *msg = static_cast<AudioKeyPressedRequest *>(msgl);
responseMsg = HandleKeyPressed(msg->step);
}
- else {
- LOG_DEBUG("Unhandled message");
- }
auto curIsBusy = IsBusy();
if (isBusy != curIsBusy) {
M module-services/service-bluetooth/ServiceBluetooth.cpp => module-services/service-bluetooth/ServiceBluetooth.cpp +0 -1
@@ 124,7 124,6 @@ sys::MessagePointer ServiceBluetooth::DataReceivedHandler(sys::DataMessage *msg,
LOG_INFO("Queues sent after a request!");
} break;
default:
- LOG_INFO("BT not handled!");
break;
}
}
M module-services/service-cellular/CMakeLists.txt => module-services/service-cellular/CMakeLists.txt +2 -1
@@ 1,4 1,4 @@
-project(service-cellular)
+project(service-cellular)
message( "${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}" )
set(SOURCES
@@ 9,6 9,7 @@ set(SOURCES
SignalStrength.cpp
SimCard.cpp
NetworkSettings.cpp
+ PacketData.cpp
RequestFactory.cpp
CellularRequestHandler.cpp
requests/Request.cpp
M module-services/service-cellular/CellularServiceAPI.cpp => module-services/service-cellular/CellularServiceAPI.cpp +55 -5
@@ 112,6 112,12 @@ void CellularServiceAPI::GetNetworkInfo(sys::Service *serv)
sys::Bus::SendUnicast(msg, ServiceCellular::serviceName, serv);
}
+void CellularServiceAPI::GetCurrentOperator(sys::Service *serv)
+{
+ std::shared_ptr<CellularGetCurrentOperatorMessage> msg = std::make_shared<CellularGetCurrentOperatorMessage>();
+ sys::Bus::SendUnicast(msg, ServiceCellular::serviceName, serv);
+}
+
void CellularServiceAPI::StartOperatorsScan(sys::Service *serv, bool fullInfo)
{
std::shared_ptr<CellularStartOperatorsScanMessage> msg =
@@ 119,6 125,21 @@ void CellularServiceAPI::StartOperatorsScan(sys::Service *serv, bool fullInfo)
sys::Bus::SendUnicast(msg, ServiceCellular::serviceName, serv);
}
+void CellularServiceAPI::SetOperatorAutoSelect(sys::Service *serv)
+{
+ auto msg = std::make_shared<CellularSetOperatorAutoSelectMessage>();
+ sys::Bus::SendUnicast(msg, ServiceCellular::serviceName, serv);
+}
+
+void CellularServiceAPI::SetOperator(sys::Service *serv,
+ at::response::cops::CopsMode mode,
+ at::response::cops::NameFormat format,
+ const std::string &name)
+{
+ auto msg = std::make_shared<CellularSetOperatorMessage>(mode, format, name);
+ sys::Bus::SendUnicast(msg, ServiceCellular::serviceName, serv);
+}
+
bool CellularServiceAPI::SelectAntenna(sys::Service *serv, bsp::cellular::antenna antenna)
{
auto msg = std::make_shared<CellularAntennaRequestMessage>(MessageType::CellularSelectAntenna);
@@ 253,13 274,11 @@ bool CellularServiceAPI::GetAntenna(sys::Service *serv, bsp::cellular::antenna &
bool CellularServiceAPI::TransmitDtmfTones(sys::Service *serv, uint32_t digit)
{
auto msg = std::make_shared<CellularDtmfRequestMessage>(digit);
-
return sys::Bus::SendUnicast(msg, ServiceCellular::serviceName, serv);
}
bool CellularServiceAPI::USSDRequest(sys::Service *serv, CellularUSSDMessage::RequestType type, std::string data)
{
-
auto msg = std::make_shared<CellularUSSDMessage>(type, data);
sys::Bus::SendUnicast(msg, ServiceCellular::serviceName, serv);
return true;
@@ 270,7 289,6 @@ bool CellularServiceAPI::ChangeSimPin(sys::Service *serv,
const std::vector<unsigned int> &passcode,
const std::vector<unsigned int> &pin)
{
-
return sys::Bus::SendUnicast(
std::make_shared<CellularSimPukDataMessage>(sim, passcode, pin), ServiceCellular::serviceName, serv);
}
@@ 280,14 298,46 @@ bool CellularServiceAPI::SetSimCardLock(sys::Service *serv,
CellularSimCardLockDataMessage::SimCardLock lock,
const std::vector<unsigned int> &pin)
{
-
return sys::Bus::SendUnicast(
std::make_shared<CellularSimCardLockDataMessage>(sim, lock, pin), ServiceCellular::serviceName, serv);
}
bool CellularServiceAPI::SetSimCard(sys::Service *serv, Store::GSM::SIM sim)
{
-
return sys::Bus::SendUnicast(
std::make_shared<CellularChangeSimDataMessage>(sim), ServiceCellular::serviceName, serv);
}
+
+bool CellularServiceAPI::GetAPN(sys::Service *serv)
+{
+ return sys::Bus::SendUnicast(std::make_shared<CellularGetAPNMessage>(), ServiceCellular::serviceName, serv);
+}
+
+bool CellularServiceAPI::GetAPN(sys::Service *serv, std::uint8_t contextId)
+{
+ return sys::Bus::SendUnicast(
+ std::make_shared<CellularGetAPNMessage>(contextId), ServiceCellular::serviceName, serv);
+}
+
+bool CellularServiceAPI::GetAPN(sys::Service *serv, packet_data::APN::APNType type)
+{
+ return sys::Bus::SendUnicast(std::make_shared<CellularGetAPNMessage>(type), ServiceCellular::serviceName, serv);
+}
+
+bool CellularServiceAPI::SetAPN(sys::Service *serv, packet_data::APN::Config apnConfig)
+{
+ auto apn = std::make_shared<packet_data::APN::Config>(std::move(apnConfig));
+ return sys::Bus::SendUnicast(std::make_shared<CellularSetAPNMessage>(apn), ServiceCellular::serviceName, serv);
+}
+
+bool CellularServiceAPI::SetDataTransfer(sys::Service *serv, packet_data::DataTransfer dt)
+{
+ return sys::Bus::SendUnicast(
+ std::make_shared<CellularSetDataTransferMessage>(dt), ServiceCellular::serviceName, serv);
+}
+
+bool CellularServiceAPI::GetDataTransfer(sys::Service *serv)
+{
+ return sys::Bus::SendUnicast(
+ std::make_shared<CellularGetDataTransferMessage>(), ServiceCellular::serviceName, serv);
+}
M module-services/service-cellular/CellularUrcHandler.cpp => module-services/service-cellular/CellularUrcHandler.cpp +18 -0
@@ 155,6 155,24 @@ void CellularUrcHandler::Handle(Cpin &urc)
}
}
+void CellularUrcHandler::Handle(Qiurc &urc)
+{
+ auto urcType = urc.getType();
+ if (urc.isValid() && urcType) {
+ switch (*urcType) {
+ case Qiurc::QIUrcMessages::DeactivateContext:
+ if (auto urcFirstParam = urc.getFirstParam(); urcFirstParam) {
+ int ctxid = 0;
+ if (utils::toNumeric(*urcFirstParam, ctxid)) {
+ response = std::make_unique<CellularDeactivateContextResponse>(at::Result::Code::OK, ctxid);
+ urc.setHandled(true);
+ }
+ }
+ break;
+ }
+ }
+}
+
void CellularUrcHandler::Handle(PoweredDown &urc)
{
if (urc.isValid()) {
M module-services/service-cellular/CellularUrcHandler.hpp => module-services/service-cellular/CellularUrcHandler.hpp +2 -0
@@ 16,6 16,7 @@
#include <module-cellular/at/UrcPoweredDown.hpp>
#include <module-cellular/at/UrcQind.hpp>
#include <module-cellular/at/UrcResponse.hpp>
+#include <module-cellular/at/UrcQiurc.hpp>
using namespace at::urc;
@@ 35,6 36,7 @@ class CellularUrcHandler : public UrcHandler
void Handle(Ctze &urc) final;
void Handle(Qind &urc) final;
void Handle(Cpin &urc) final;
+ void Handle(Qiurc &urc) final;
void Handle(PoweredDown &urc) final;
void Handle(UrcResponse &urc) final;
M module-services/service-cellular/NetworkSettings.cpp => module-services/service-cellular/NetworkSettings.cpp +44 -0
@@ 5,6 5,22 @@
#include <unordered_map>
+std::string NetworkSettings::getCurrentOperator() const
+{
+ auto channel = cellularService.cmux->get(TS0710::Channel::Commands);
+ if (channel) {
+ at::Cmd buildCmd = at::factory(at::AT::COPS) + "?";
+ auto resp = channel->cmd(buildCmd);
+ at::response::cops::CurrentOperatorInfo ret;
+ if ((resp.code == at::Result::Code::OK) && (at::response::parseCOPS(resp, ret))) {
+ if (auto _operator = ret.getOperator(); _operator) {
+ return _operator->getNameByFormat(ret.getFormat());
+ }
+ }
+ }
+
+ return {};
+}
std::vector<std::string> NetworkSettings::scanOperators(bool fullInfoList)
{
std::vector<std::string> operatorNames;
@@ 54,3 70,31 @@ std::vector<std::string> NetworkSettings::scanOperators(bool fullInfoList)
return operatorNames;
}
+
+bool NetworkSettings::setOperatorAutoSelect()
+{
+ auto channel = cellularService.cmux->get(TS0710::Channel::Commands);
+ if (!channel) {
+ return false;
+ }
+
+ at::Cmd buildCmd =
+ at::factory(at::AT::COPS) + "=" + utils::to_string(static_cast<int>(at::response::cops::CopsMode::Automatic));
+ auto resp = channel->cmd(buildCmd);
+ return (resp.code == at::Result::Code::OK);
+}
+bool NetworkSettings::setOperator(at::response::cops::CopsMode mode,
+ at::response::cops::NameFormat format,
+ const std::string &name)
+{
+ auto channel = cellularService.cmux->get(TS0710::Channel::Commands);
+ if (!channel) {
+ return false;
+ }
+
+ at::Cmd buildCmd = at::factory(at::AT::COPS) + "=" + utils::to_string(static_cast<int>(mode)) + "," +
+ utils::to_string(static_cast<int>(format)) + ",\"" + name + "\"";
+
+ auto resp = channel->cmd(buildCmd);
+ return (resp.code == at::Result::Code::OK);
+}
M module-services/service-cellular/NetworkSettings.hpp => module-services/service-cellular/NetworkSettings.hpp +4 -0
@@ 25,6 25,10 @@ class NetworkSettings
*/
std::vector<std::string> scanOperators(bool fullInfoList = false);
+ bool setOperatorAutoSelect();
+ std::string getCurrentOperator() const;
+ bool setOperator(at::response::cops::CopsMode mode, at::response::cops::NameFormat format, const std::string &name);
+
private:
ServiceCellular &cellularService;
};
A module-services/service-cellular/PacketData.cpp => module-services/service-cellular/PacketData.cpp +317 -0
@@ 0,0 1,317 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "PacketData.hpp"
+
+#include <optional>
+#include <algorithm>
+#include <iterator>
+
+#include <response.hpp>
+#include <Utils.hpp>
+namespace at
+{
+ namespace response
+ {
+ bool parseQICSGP(const at::Result &resp, std::shared_ptr<packet_data::APN::Config> retAPN)
+ {
+
+ /// AT+QICSGP=<contextID>[,<context_type>,<APN>[,<username>,<password>)[,<authentication>[,<cdma_pwd>]]]]
+ /// +QICSGP: <context_type>,<APN>,<username>,<password>,<authentication>
+ /// +QICSGP: 1,"","","",0
+ constexpr auto AT_QICSGP = "+QICSGP:";
+ if (auto tokens = at::response::getTokensForATCommand(resp, AT_QICSGP); tokens) {
+
+ constexpr int QICSGP_TokensCount = 5; /// Could be depend ? on firmware version, assume that always have
+ /// <context_type>,<APN>,<username>,<password>,<authentication>
+ if ((*tokens).size() == QICSGP_TokensCount) {
+
+ retAPN->contextType =
+ static_cast<packet_data::APN::ContextType>(utils::getNumericValue<unsigned int>((*tokens)[0]));
+ retAPN->apn = (*tokens)[1];
+ utils::findAndReplaceAll(retAPN->apn, at::response::StringDelimiter, "");
+ retAPN->username = (*tokens)[2];
+ utils::findAndReplaceAll(retAPN->username, at::response::StringDelimiter, "");
+ retAPN->password = (*tokens)[3];
+ utils::findAndReplaceAll(retAPN->password, at::response::StringDelimiter, "");
+ retAPN->authMethod =
+ static_cast<packet_data::APN::AuthMethod>(utils::getNumericValue<unsigned int>((*tokens)[4]));
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool parseQIACT(const at::Result &resp, std::vector<std::shared_ptr<packet_data::APN::Config>> &ret)
+ {
+
+ /**
+ * In case of AT+QIACT?
+ * +QIACT:1,<context_state>,<context_type>[,<IP_address>]
+ * [.....
+ * +QIACT:16,<context_state>,<context_type>[,<IP_address>]]
+ *
+ * also could be empty list
+ */
+ constexpr int QIACT_TokensCount = 3;
+ constexpr int QIACT_WithIP_TokensCount = 4;
+
+ constexpr auto AT_QIACT = "+QIACT:";
+ if (auto mtokens = at::response::getTokensForATResults(resp, AT_QIACT); mtokens) {
+ for (const auto &tokens : *mtokens) {
+
+ if (tokens.size() < QIACT_TokensCount) {
+ continue;
+ }
+ std::shared_ptr<packet_data::APN::Config> retAPN = std::make_shared<packet_data::APN::Config>();
+ retAPN->contextId = utils::getNumericValue<std::uint8_t>(tokens[0]);
+ retAPN->state =
+ static_cast<packet_data::APN::ContextState>(utils::getNumericValue<std::uint8_t>(tokens[1]));
+ retAPN->contextType =
+ static_cast<packet_data::APN::ContextType>(utils::getNumericValue<std::uint8_t>(tokens[2]));
+
+ if (tokens.size() >= QIACT_WithIP_TokensCount) {
+ retAPN->ip = tokens[3];
+ utils::findAndReplaceAll(retAPN->ip, at::response::StringDelimiter, "");
+ }
+ ret.push_back(retAPN);
+ }
+ return true; /// empty list is also good result
+ }
+ return false;
+ }
+ } // namespace response
+ namespace query
+ {
+ std::string prepareQICSGP(std::shared_ptr<packet_data::APN::Config> apn, bool setEmpty)
+ {
+ /// AT+QICSGP=<contextID>[,<context_type>,<APN>[,<username>,<password>)[,<authentication>]]]
+
+ if (setEmpty) {
+ LOG_DEBUG("Set empty APN");
+ return at::factory(at::AT::QICSGP) + "=" + utils::to_string(static_cast<int>(apn->contextId)) + "," +
+ "1,\"\",\"\",\"\",1";
+ }
+
+ return at::factory(at::AT::QICSGP) + "=" + utils::to_string(static_cast<int>(apn->contextId)) + "," +
+ utils::to_string(static_cast<int>(apn->contextType)) + ",\"" + apn->apn + "\",\"" + apn->username +
+ "\",\"" + apn->password + "\"," + utils::to_string(static_cast<int>(apn->authMethod));
+ }
+
+ } // namespace query
+} // namespace at
+
+namespace packet_data
+{
+
+ PDPContext::PDPContext(ServiceCellular &cellularService) : cellularService(cellularService)
+ {
+ channel = cellularService.cmux->get(TS0710::Channel::Commands);
+ }
+
+ std::shared_ptr<APN::Config> PDPContext::getConfiguration(std::uint8_t contextId)
+ {
+ if (channel) {
+ auto resp = channel->cmd(at::factory(at::AT::QICSGP) + "=" + utils::to_string(static_cast<int>(contextId)));
+ if (resp.code == at::Result::Code::OK) {
+ std::shared_ptr<APN::Config> ret = std::make_shared<APN::Config>();
+ ret->contextId = contextId;
+ if (at::response::parseQICSGP(resp, ret)) {
+ return ret;
+ }
+ }
+ }
+ return nullptr;
+ }
+
+ at::Result::Code PDPContext::setConfiguration(std::shared_ptr<APN::Config> apn, bool setEmpty)
+ {
+ if (channel) {
+ auto resp = channel->cmd(at::query::prepareQICSGP(apn, setEmpty));
+ return resp.code;
+ }
+ return at::Result::Code::ERROR;
+ }
+
+ at::Result::Code PDPContext::activate(std::uint8_t contextId)
+ {
+
+ if (channel) {
+ auto resp = channel->cmd(at::factory(at::AT::QIACT) + "=" + utils::to_string(static_cast<int>(contextId)));
+
+ /**
+ * From quectel documentation:
+ * 1. Reboot the module if there is no response in 150s.
+ * 2. If failed to deactivate the PDP context for 3 times continuously, then reboot the module.
+ */
+ return resp.code;
+ }
+ return at::Result::Code::ERROR;
+ }
+ at::Result::Code PDPContext::deactivate(std::uint8_t contextId)
+ {
+ if (channel) {
+ /// this command could generate URC, deactivate
+ auto resp =
+ channel->cmd(at::factory(at::AT::QIDEACT) + "=" + utils::to_string(static_cast<int>(contextId)));
+
+ return resp.code;
+ }
+ return at::Result::Code::ERROR;
+ }
+
+ std::optional<const std::vector<std::shared_ptr<APN::Config>>> PDPContext::getActive()
+ {
+ if (channel) {
+ auto resp = channel->cmd(at::factory(at::AT::QIACT) + "?");
+
+ if (resp.code == at::Result::Code::OK) {
+ std::vector<std::shared_ptr<APN::Config>> ret;
+ if (at::response::parseQIACT(resp, ret)) {
+ return ret;
+ }
+ }
+ }
+ return std::nullopt;
+ }
+
+ PacketData::PacketData(ServiceCellular &cellularService) : cellularService(cellularService){};
+
+ void PacketData::loadAPNSettings()
+ {
+
+ std::shared_ptr<APN::Config> apnConfig = std::make_shared<APN::Config>();
+ apnConfig->contextId = 1;
+ apnConfig->apnType = APN::APNType::Default;
+ apnConfig->apn = "internet";
+ apnConfig->authMethod = APN::AuthMethod::NONE;
+
+ contextMap[apnConfig->contextId] = apnConfig;
+
+ LOG_ERROR("loadAPNSettings");
+ }
+
+ void PacketData::saveAPNSettings()
+ {
+ /// Save in phone memory
+ }
+
+ at::Result::Code PacketData::updateAPNSettings(std::uint8_t ctxId)
+ {
+ LOG_DEBUG("updateAPNSettings %d", ctxId);
+ PDPContext pdpCtx(cellularService);
+ std::shared_ptr<APN::Config> apnConfig;
+ std::shared_ptr<APN::Config> modemApn;
+ if ((modemApn = pdpCtx.getConfiguration(ctxId)) && (modemApn != nullptr)) {
+
+ auto phoneApn = contextMap.find(ctxId);
+
+ if (phoneApn != contextMap.end()) {
+ LOG_DEBUG("Phone context exists");
+ if (dataTransfer == DataTransfer::Off) {
+ /// set null configuration, solution based on lack of quectel documentation
+ return pdpCtx.setConfiguration(phoneApn->second, true);
+ }
+ else {
+ if (!phoneApn->second->compare(modemApn)) {
+ /// rebuild configuratio
+ LOG_DEBUG("Update modem context %d", ctxId);
+ return pdpCtx.setConfiguration(phoneApn->second);
+ }
+ }
+ }
+ else {
+ LOG_ERROR("Phone context not exists");
+ if (!modemApn->isEmpty()) {
+ /** update phone configuration base on modem conf (eg. ims)
+ *
+ * Comment from quectel (2020.12):
+ * As I know, only VZW MBN would use IMS on CID 1 to register IMS service directly.
+ * So we usually ask customers to set up data connection on CID3 for VZW sim card.
+ * Regarding other MBN files, the CID 1 shouldn’t be IMS because the CID1 is used
+ * to activate default bearer for LTE network. So we usually ask customers to
+ * configure their own APN on CID1. With this rule, it’s easy for customer
+ * to configure their APN no matter which MBN file is activated
+ */
+ LOG_ERROR("Update modem context %d", ctxId);
+ return pdpCtx.setConfiguration(modemApn, true);
+ }
+ }
+ }
+ return at::Result::Code::OK;
+ }
+ void PacketData::setupAPNSettings()
+ {
+ for (std::uint8_t ctxId = MINContextId; ctxId <= MAXContextId; ctxId++) {
+ updateAPNSettings(ctxId);
+ }
+ }
+
+ std::optional<std::shared_ptr<APN::Config>> PacketData::getAPN(std::uint8_t ctxid)
+ {
+ auto apn = std::find_if(contextMap.begin(), contextMap.end(), [&ctxid](const ContextPair &pair) {
+ return pair.second->contextId == ctxid;
+ });
+
+ if (apn != contextMap.end()) {
+ return apn->second;
+ }
+
+ return std::nullopt;
+ }
+
+ const std::vector<std::shared_ptr<APN::Config>> PacketData::getAPNs() const
+ {
+ std::vector<std::shared_ptr<APN::Config>> vconf;
+ std::transform(
+ contextMap.begin(), contextMap.end(), std::back_inserter(vconf), [](auto &cm) { return cm.second; });
+ return vconf;
+ }
+
+ std::optional<std::shared_ptr<APN::Config>> PacketData::getAPNFirst(APN::APNType type)
+ {
+
+ auto apn = std::find_if(contextMap.begin(), contextMap.end(), [&type](const ContextPair &pair) -> bool {
+ return pair.second->apnType == type;
+ });
+
+ if (apn != contextMap.end()) {
+ return apn->second;
+ }
+
+ return std::nullopt;
+ }
+
+ at::Result::Code PacketData::setAPN(std::shared_ptr<APN::Config> apn)
+ {
+ contextMap[apn->contextId] = apn;
+ return updateAPNSettings(apn->contextId);
+ }
+
+ bool PacketData::setDataTransfer(DataTransfer dt)
+ {
+ dataTransfer = dt;
+ setupAPNSettings();
+ return true;
+ }
+ DataTransfer PacketData::getDataTransfer() const
+ {
+ return dataTransfer;
+ }
+
+ std::optional<const std::vector<std::shared_ptr<APN::Config>>> PacketData::getActiveContexts()
+ {
+ PDPContext pdpCtx(cellularService);
+ return pdpCtx.getActive();
+ }
+ at::Result::Code PacketData::activateContext(std::uint8_t contextId)
+ {
+ PDPContext pdpCtx(cellularService);
+ return pdpCtx.activate(contextId);
+ }
+ at::Result::Code PacketData::deactivateContext(std::uint8_t contextId)
+ {
+ PDPContext pdpCtx(cellularService);
+ return pdpCtx.deactivate(contextId);
+ }
+} // namespace packet_data
A module-services/service-cellular/PacketData.hpp => module-services/service-cellular/PacketData.hpp +104 -0
@@ 0,0 1,104 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <string>
+#include <unordered_map>
+#include "PacketData.hpp"
+
+#include "service-cellular/PacketDataTypes.hpp"
+#include "service-cellular/ServiceCellular.hpp"
+
+namespace at
+{
+ namespace response
+ {
+ /// Separate, specific AT command for quectel, only for TCP/PDP connection/configuration
+
+ bool parseQICSGP(const at::Result &resp, std::shared_ptr<packet_data::APN::Config> retAPN);
+ bool parseQIACT(const at::Result &resp, std::vector<std::shared_ptr<packet_data::APN::Config>> &ret);
+
+ } // namespace response
+ namespace query
+ {
+ std::string prepareQICSGP(std::shared_ptr<packet_data::APN::Config> apn, bool setEmpty = false);
+ }
+} // namespace at
+
+namespace packet_data
+{
+
+ class PDPContext
+ {
+
+ private:
+ ServiceCellular &cellularService;
+ DLC_channel *channel = nullptr;
+
+ public:
+ explicit PDPContext(ServiceCellular &cellularService);
+ at::Result::Code setConfiguration(std::shared_ptr<APN::Config> apn, bool setEmpty = false);
+ std::shared_ptr<APN::Config> getConfiguration(std::uint8_t contextId);
+ at::Result::Code activate(std::uint8_t contextId);
+ at::Result::Code deactivate(std::uint8_t contextId);
+ std::optional<const std::vector<std::shared_ptr<APN::Config>>> getActive();
+ };
+
+ /**
+ * @brief wrapping class, transfer data (APN) settings. Responsible for maintaining the state
+ * between the modem and the settings
+ */
+ class PacketData
+ {
+ private:
+ ServiceCellular &cellularService;
+ ContextMap contextMap;
+ DataTransfer dataTransfer = DataTransfer::On; /// depend on APN settings
+
+ public:
+ explicit PacketData(ServiceCellular &cellularService);
+ /**
+ * @brief load APN settings from phone memory, not call modem func.
+ *
+ * To synchronize with modem, this function should be call as soon as
+ * the modem is ready (in sense of AT commands). Then should be call
+ * setupAPNSettings to synchronize modem database with phone database.
+ */
+ void loadAPNSettings();
+
+ /**
+ * @brief save all APN's in phone memory
+ */
+ void saveAPNSettings();
+
+ /**
+ * @brief setup APN on modem, based on configuration loaded in loadAPNSettings
+ * cleanup old configuration
+ */
+ void setupAPNSettings();
+
+ at::Result::Code updateAPNSettings(std::uint8_t ctxId);
+
+ std::optional<const std::vector<std::shared_ptr<APN::Config>>> getActiveContexts();
+ at::Result::Code activateContext(std::uint8_t contextId);
+ at::Result::Code deactivateContext(std::uint8_t contextId);
+
+ bool setDataTransfer(DataTransfer dt);
+ DataTransfer getDataTransfer() const;
+ /**
+ * @brief get one APN from phone configuration, connected with ctxid
+ */
+ std::optional<std::shared_ptr<APN::Config>> getAPN(std::uint8_t ctxid);
+ /**
+ * @brief get all APNs from phone configuration
+ */
+ const std::vector<std::shared_ptr<APN::Config>> getAPNs() const;
+ /**
+ * @brief get first APN with type, from phone configuration
+ */
+ std::optional<std::shared_ptr<APN::Config>> getAPNFirst(APN::APNType type);
+ at::Result::Code setAPN(std::shared_ptr<APN::Config> apn);
+ };
+
+} // namespace packet_data
M module-services/service-cellular/ServiceCellular.cpp => module-services/service-cellular/ServiceCellular.cpp +141 -1
@@ 102,6 102,8 @@ const char *State::c_str(State::ST state)
return "PowerUpProcedure";
case ST::AudioConfigurationProcedure:
return "AudioConfigurationProcedure";
+ case ST::APNConfProcedure:
+ return "APNConfProcedure";
case ST::ModemOn:
return "ModemOn";
case ST::URCReady:
@@ 196,6 198,9 @@ ServiceCellular::ServiceCellular() : sys::Service(serviceName, "", cellularStack
sys::Bus::SendMulticast(msg.value(), sys::BusChannels::ServiceCellularNotifications, this);
};
registerMessageHandlers();
+
+ packetData = std::make_unique<packet_data::PacketData>(*this);
+ packetData->loadAPNSettings();
}
ServiceCellular::~ServiceCellular()
@@ 259,8 264,10 @@ sys::ReturnCodes ServiceCellular::SwitchPowerModeHandler(const sys::ServicePower
return sys::ReturnCodes::Success;
}
+
void handleCellularSimNewPinDataMessage(CellularSimNewPinDataMessage *msg)
{}
+
void ServiceCellular::registerMessageHandlers()
{
connect(typeid(CellularSimNewPinDataMessage), [&](sys::Message *request) -> sys::MessagePointer {
@@ 287,6 294,45 @@ void ServiceCellular::registerMessageHandlers()
auto msg = static_cast<CellularStartOperatorsScanMessage *>(request);
return handleCellularStartOperatorsScan(msg);
});
+ connect(typeid(CellularGetActiveContextsMessage), [&](sys::Message *request) -> sys::MessagePointer {
+ auto msg = static_cast<CellularGetActiveContextsMessage *>(request);
+ return handleCellularGetActiveContextsMessage(msg);
+ });
+
+ connect(typeid(CellularGetAPNMessage), [&](sys::Message *request) -> sys::MessagePointer {
+ connect(typeid(CellularGetCurrentOperatorMessage), [&](sys::Message *request) -> sys::MessagePointer {
+ auto msg = static_cast<CellularGetCurrentOperatorMessage *>(request);
+ return handleCellularGetCurrentOperator(msg);
+ });
+
+ auto msg = static_cast<CellularGetAPNMessage *>(request);
+ return handleCellularGetAPNMessage(msg);
+ });
+
+ connect(typeid(CellularSetDataTransferMessage), [&](sys::Message *request) -> sys::MessagePointer {
+ auto msg = static_cast<CellularSetDataTransferMessage *>(request);
+ return handleCellularSetDataTransferMessage(msg);
+ });
+
+ connect(typeid(CellularGetDataTransferMessage), [&](sys::Message *request) -> sys::MessagePointer {
+ auto msg = static_cast<CellularGetDataTransferMessage *>(request);
+ return handleCellularGetDataTransferMessage(msg);
+ });
+
+ connect(typeid(CellularActivateContextMessage), [&](sys::Message *request) -> sys::MessagePointer {
+ auto msg = static_cast<CellularActivateContextMessage *>(request);
+ return handleCellularActivateContextMessage(msg);
+ });
+
+ connect(typeid(CellularDeactivateContextMessage), [&](sys::Message *request) -> sys::MessagePointer {
+ auto msg = static_cast<CellularDeactivateContextMessage *>(request);
+ return handleCellularDeactivateContextMessage(msg);
+ });
+
+ connect(typeid(CellularGetActiveContextsMessage), [&](sys::Message *request) -> sys::MessagePointer {
+ auto msg = static_cast<CellularGetActiveContextsMessage *>(request);
+ return handleCellularGetActiveContextsMessage(msg);
+ });
handle_CellularGetChannelMessage();
}
@@ 342,6 388,9 @@ void ServiceCellular::change_state(cellular::StateChange *msg)
case State::ST::CellularConfProcedure:
handle_start_conf_procedure();
break;
+ case State::ST::APNConfProcedure:
+ handle_apn_conf_procedure();
+ break;
case State::ST::SanityCheck:
handle_sim_sanity_check();
break;
@@ 531,7 580,7 @@ bool ServiceCellular::handle_audio_conf_procedure()
LOG_DEBUG("Setting up notifications callback");
notificationsChannel->setCallback(notificationCallback);
}
- state.set(this, State::ST::SanityCheck);
+ state.set(this, State::ST::APNConfProcedure);
return true;
}
else {
@@ 1991,3 2040,94 @@ std::shared_ptr<cellular::RawCommandRespAsync> ServiceCellular::handleCellularSt
sys::Bus::SendUnicast(ret, msg->sender, this);
return ret;
}
+bool ServiceCellular::handle_apn_conf_procedure()
+{
+ LOG_DEBUG("APN on modem configuration");
+ packetData->setupAPNSettings();
+ state.set(this, State::ST::SanityCheck);
+ return true;
+}
+
+std::shared_ptr<CellularGetCurrentOperatorResponse> ServiceCellular::handleCellularGetCurrentOperator(
+ CellularGetCurrentOperatorMessage *msg)
+{
+ LOG_INFO("CellularGetCurrentOperator handled");
+ NetworkSettings networkSettings(*this);
+ return std::make_shared<CellularGetCurrentOperatorResponse>(networkSettings.getCurrentOperator());
+}
+
+std::shared_ptr<CellularGetAPNResponse> ServiceCellular::handleCellularGetAPNMessage(CellularGetAPNMessage *msg)
+{
+ std::vector<std::shared_ptr<packet_data::APN::Config>> apns;
+
+ if (auto type = msg->getAPNType(); type) {
+ if (auto apn = packetData->getAPNFirst(*type); apn) {
+ apns.push_back(*apn);
+ }
+ return std::make_shared<CellularGetAPNResponse>(apns);
+ }
+
+ if (auto ctxid = msg->getContextId(); ctxid) {
+ if (auto apn = packetData->getAPN(*ctxid); apn) {
+ apns.push_back(*apn);
+ }
+ return std::make_shared<CellularGetAPNResponse>(apns);
+ }
+
+ return std::make_shared<CellularGetAPNResponse>(packetData->getAPNs());
+}
+std::shared_ptr<CellularSetAPNResponse> ServiceCellular::handleCellularSetAPNMessage(CellularSetAPNMessage *msg)
+{
+
+ auto apn = msg->getAPNConfig();
+
+ return std::make_shared<CellularSetAPNResponse>(packetData->setAPN(apn));
+}
+std::shared_ptr<CellularSetDataTransferResponse> ServiceCellular::handleCellularSetDataTransferMessage(
+ CellularSetDataTransferMessage *msg)
+{
+ packetData->setDataTransfer(msg->getDataTransfer());
+ return std::make_shared<CellularSetDataTransferResponse>(at::Result::Code::OK);
+}
+std::shared_ptr<CellularGetDataTransferResponse> ServiceCellular::handleCellularGetDataTransferMessage(
+ CellularGetDataTransferMessage *msg)
+{
+ return std::make_shared<CellularGetDataTransferResponse>(packetData->getDataTransfer());
+}
+
+std::shared_ptr<CellularActivateContextResponse> ServiceCellular::handleCellularActivateContextMessage(
+ CellularActivateContextMessage *msg)
+{
+ return std::make_shared<CellularActivateContextResponse>(packetData->activateContext(msg->getContextId()),
+ msg->getContextId());
+}
+std::shared_ptr<CellularDeactivateContextResponse> ServiceCellular::handleCellularDeactivateContextMessage(
+ CellularDeactivateContextMessage *msg)
+{
+ return std::make_shared<CellularDeactivateContextResponse>(packetData->deactivateContext(msg->getContextId()),
+ msg->getContextId());
+}
+
+std::shared_ptr<CellularGetActiveContextsResponse> ServiceCellular::handleCellularGetActiveContextsMessage(
+ CellularGetActiveContextsMessage *msg)
+{
+ return std::make_shared<CellularGetActiveContextsResponse>(packetData->getActiveContexts());
+}
+
+std::shared_ptr<CellularSetOperatorAutoSelectResponse> ServiceCellular::handleCellularSetOperatorAutoSelect(
+ CellularSetOperatorAutoSelectMessage *msg)
+{
+ LOG_INFO("CellularSetOperatorAutoSelect handled");
+
+ NetworkSettings networkSettings(*this);
+ return std::make_shared<CellularSetOperatorAutoSelectResponse>(networkSettings.setOperatorAutoSelect());
+}
+
+std::shared_ptr<CellularSetOperatorResponse> ServiceCellular::handleCellularSetOperator(CellularSetOperatorMessage *msg)
+{
+ LOG_INFO("CellularSetOperatorAutoSelect handled");
+
+ NetworkSettings networkSettings(*this);
+ return std::make_shared<CellularSetOperatorResponse>(
+ networkSettings.setOperator(msg->getMode(), msg->getFormat(), msg->getName()));
+}
M module-services/service-cellular/service-cellular/CellularMessage.hpp => module-services/service-cellular/service-cellular/CellularMessage.hpp +74 -1
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
@@ 14,6 14,8 @@
#include <utf8/UTF8.hpp>
#include <SimState.hpp>
+#include <response.hpp>
+
#include <memory>
#include <string>
@@ 86,6 88,62 @@ class CellularNotificationMessage : public CellularMessage
std::string data;
};
+class CellularGetCurrentOperatorMessage : public CellularMessage
+{
+ public:
+ explicit CellularGetCurrentOperatorMessage() : CellularMessage(MessageType::CellularNotification)
+ {}
+};
+
+class CellularSetOperatorAutoSelectMessage : public sys::Message
+{
+ public:
+ CellularSetOperatorAutoSelectMessage()
+ {}
+};
+
+class CellularGetCurrentOperatorResponse : public CellularMessage
+{
+ std::string currentOperatorName;
+
+ public:
+ explicit CellularGetCurrentOperatorResponse(std::string currentOperatorName)
+ : CellularMessage(MessageType::CellularNotification), currentOperatorName(currentOperatorName)
+ {}
+
+ std::string getCurrentOperatorName() const
+ {
+ return currentOperatorName;
+ }
+};
+class CellularSetOperatorMessage : public sys::Message
+{
+ at::response::cops::CopsMode mode;
+ at::response::cops::NameFormat format;
+ const std::string name;
+
+ public:
+ explicit CellularSetOperatorMessage(at::response::cops::CopsMode mode,
+ at::response::cops::NameFormat format,
+ std::string name)
+ : mode(mode), format(format), name(std::move(name))
+ {}
+
+ at::response::cops::CopsMode getMode() const noexcept
+ {
+ return mode;
+ }
+ at::response::cops::NameFormat getFormat() const noexcept
+ {
+ return format;
+ }
+
+ std::string getName() const
+ {
+ return name;
+ }
+};
+
class CellularStartOperatorsScanMessage : public CellularMessage
{
bool fullInfo = false;
@@ 622,6 680,21 @@ class CellularMMIPushMessage : public CellularMMIDataMessage, public app::manage
sender, app::manager::actions::ShowMMIPush, std::make_unique<app::manager::actions::MMIParams>(params));
}
};
+
+class CellularSetOperatorAutoSelectResponse : public CellularResponseMessage
+{
+ public:
+ explicit CellularSetOperatorAutoSelectResponse(bool ret) : CellularResponseMessage(ret)
+ {}
+};
+
+class CellularSetOperatorResponse : public CellularResponseMessage
+{
+ public:
+ explicit CellularSetOperatorResponse(bool ret) : CellularResponseMessage(ret)
+ {}
+};
+
namespace cellular
{
M module-services/service-cellular/service-cellular/CellularServiceAPI.hpp => module-services/service-cellular/service-cellular/CellularServiceAPI.hpp +31 -0
@@ 4,6 4,7 @@
#pragma once
#include "CellularMessage.hpp"
+#include "PacketDataCellularMessage.hpp"
#include <Modem/TS0710/TS0710.h>
#include <PhoneNumber.hpp>
@@ 42,6 43,12 @@ namespace CellularServiceAPI
* @param serv pointer to caller service.
*/
void GetNetworkInfo(sys::Service *serv);
+
+ /*
+ * @brief Get current operator, result async in
+ * CellularGetCurrentOperatorResponse message
+ */
+ void GetCurrentOperator(sys::Service *serv);
/*
* @brief It calls service-cellulat to perform operators scan
* @param serv pointer to caller service.
@@ 49,6 56,13 @@ namespace CellularServiceAPI
*
*/
void StartOperatorsScan(sys::Service *serv, bool fullInfo = false);
+
+ void SetOperatorAutoSelect(sys::Service *serv);
+ void SetOperator(sys::Service *serv,
+ at::response::cops::CopsMode mode,
+ at::response::cops::NameFormat format,
+ const std::string &name);
+
/*
* @brief It calls service-cellulat to switch antenna
* @param serv pointer to caller service.
@@ 80,4 94,21 @@ namespace CellularServiceAPI
CellularSimCardLockDataMessage::SimCardLock lock,
const std::vector<unsigned int> &pin);
bool SetSimCard(sys::Service *serv, Store::GSM::SIM sim);
+
+ /**
+ * @brief get all APNs from phone configuration
+ */
+ bool GetAPN(sys::Service *serv);
+ /**
+ * @brief get one APN from phone configuration, connected with ctxid
+ */
+ bool GetAPN(sys::Service *serv, std::uint8_t contextId);
+ /**
+ * @brief get first APN with type, from phone configuration
+ */
+ bool GetAPN(sys::Service *serv, packet_data::APN::APNType type);
+
+ bool SetAPN(sys::Service *serv, packet_data::APN::Config apnConfig);
+ bool SetDataTransfer(sys::Service *serv, packet_data::DataTransfer dt);
+ bool GetDataTransfer(sys::Service *serv);
}; // namespace CellularServiceAPI
A module-services/service-cellular/service-cellular/PacketDataCellularMessage.hpp => module-services/service-cellular/service-cellular/PacketDataCellularMessage.hpp +224 -0
@@ 0,0 1,224 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <string>
+#include <optional>
+#include "CellularMessage.hpp"
+#include "MessageType.hpp"
+
+#include "PacketDataTypes.hpp"
+#include "Result.hpp"
+
+class CellularGetAPNMessage : public CellularMessage
+{
+ std::optional<std::uint8_t> contextId = std::nullopt;
+ std::optional<packet_data::APN::APNType> apnType = std::nullopt;
+
+ public:
+ CellularGetAPNMessage() : CellularMessage(MessageType::CellularPacketData)
+ {}
+ CellularGetAPNMessage(std::uint8_t contextId)
+ : CellularMessage(MessageType::CellularPacketData), contextId(contextId)
+ {}
+ CellularGetAPNMessage(packet_data::APN::APNType apnType)
+ : CellularMessage(MessageType::CellularPacketData), apnType(apnType)
+ {}
+ [[nodiscard]] const std::optional<packet_data::APN::APNType> getAPNType() const noexcept
+ {
+ return apnType;
+ }
+ [[nodiscard]] const std::optional<std::uint8_t> getContextId() const noexcept
+ {
+ return contextId;
+ }
+};
+
+class CellularSetAPNMessage : public CellularMessage // also as DeleteAPN
+{
+ std::shared_ptr<packet_data::APN::Config> apnConfig;
+
+ public:
+ CellularSetAPNMessage(std::shared_ptr<packet_data::APN::Config> apnConfig)
+ : CellularMessage(MessageType::CellularPacketData), apnConfig(apnConfig)
+ {}
+
+ [[nodiscard]] std::shared_ptr<packet_data::APN::Config> getAPNConfig() const noexcept
+ {
+ return apnConfig;
+ }
+};
+
+class CellularSetDataTransferMessage : public CellularMessage
+{
+ packet_data::DataTransfer dataTransfer;
+
+ public:
+ CellularSetDataTransferMessage(packet_data::DataTransfer dataTransfer)
+ : CellularMessage(MessageType::CellularPacketData), dataTransfer(dataTransfer)
+ {}
+ [[nodiscard]] packet_data::DataTransfer getDataTransfer() const noexcept
+ {
+ return dataTransfer;
+ }
+};
+
+class CellularGetDataTransferMessage : public CellularMessage
+{
+ public:
+ CellularGetDataTransferMessage() : CellularMessage(MessageType::CellularPacketData)
+ {}
+};
+
+class CellularGetActiveContextsMessage : public CellularMessage
+{
+ public:
+ CellularGetActiveContextsMessage() : CellularMessage(MessageType::CellularPacketData)
+ {}
+};
+
+class CellularActivateContextMessage : public CellularMessage
+{
+ std::uint8_t contextId;
+
+ public:
+ CellularActivateContextMessage(std::uint8_t contextId)
+ : CellularMessage(MessageType::CellularPacketData), contextId(contextId)
+ {}
+ [[nodiscard]] std::uint8_t getContextId() const noexcept
+ {
+ return contextId;
+ }
+};
+class CellularDeactivateContextMessage : public CellularMessage
+{
+ std::uint8_t contextId;
+
+ public:
+ CellularDeactivateContextMessage(std::uint8_t contextId)
+ : CellularMessage(MessageType::CellularPacketData), contextId(contextId)
+ {}
+ [[nodiscard]] std::uint8_t getContextId() const noexcept
+ {
+ return contextId;
+ }
+};
+
+// Send from Cellular
+class CellularGetAPNResponse : public CellularMessage
+{
+ std::vector<std::shared_ptr<packet_data::APN::Config>> apns;
+
+ public:
+ CellularGetAPNResponse(std::vector<std::shared_ptr<packet_data::APN::Config>> apns)
+ : CellularMessage(MessageType::CellularPacketData), apns(std::move(apns))
+ {}
+
+ [[nodiscard]] const std::vector<std::shared_ptr<packet_data::APN::Config>> &getAPNs() const noexcept
+ {
+ return apns;
+ }
+};
+
+class CellularATResponse : public CellularMessage
+{
+ at::Result::Code result;
+
+ public:
+ CellularATResponse(at::Result::Code result) : CellularMessage(MessageType::CellularPacketData), result(result)
+ {}
+
+ [[nodiscard]] at::Result::Code getResult() const noexcept
+ {
+ return result;
+ }
+};
+
+class CellularSetAPNResponse : public CellularATResponse
+{
+ public:
+ CellularSetAPNResponse(at::Result::Code result) : CellularATResponse(result)
+ {}
+};
+
+class CellularSetDataTransferResponse : public CellularATResponse
+{
+ public:
+ CellularSetDataTransferResponse(at::Result::Code result) : CellularATResponse(result)
+ {}
+};
+
+class CellularGetDataTransferResponse : public CellularMessage
+{
+ packet_data::DataTransfer dataTransfer;
+
+ public:
+ CellularGetDataTransferResponse(packet_data::DataTransfer dataTransfer)
+ : CellularMessage(MessageType::CellularPacketData), dataTransfer(dataTransfer)
+ {}
+
+ [[nodiscard]] packet_data::DataTransfer getDataTransfer() const noexcept
+ {
+ return dataTransfer;
+ }
+};
+
+class CellularGetActiveContextsResponse : public CellularMessage
+{
+ std::optional<std::vector<std::shared_ptr<packet_data::APN::Config>>> result;
+
+ public:
+ CellularGetActiveContextsResponse(std::optional<std::vector<std::shared_ptr<packet_data::APN::Config>>> result)
+ : CellularMessage(MessageType::CellularPacketData)
+ {
+ if (result) {
+ result = std::move(*result);
+ }
+ else {
+ result = std::nullopt;
+ }
+ }
+
+ [[nodiscard]] std::optional<const std::vector<std::shared_ptr<packet_data::APN::Config>>> getActive()
+ {
+ return result;
+ }
+};
+
+class CellularActivateContextResponse : public CellularATResponse
+{
+ std::uint8_t contextId;
+
+ public:
+ CellularActivateContextResponse(at::Result::Code result, std::uint8_t contextId)
+ : CellularATResponse(result), contextId(contextId)
+ {}
+ [[nodiscard]] std::uint8_t getContextId() const noexcept
+ {
+ return contextId;
+ }
+};
+class CellularDeactivateContextResponse : public CellularATResponse
+{
+ std::uint8_t contextId;
+ bool isUrc;
+
+ public:
+ CellularDeactivateContextResponse(at::Result::Code result, std::uint8_t contextId, bool isUrc = false)
+ : CellularATResponse(result), contextId(contextId), isUrc(isUrc)
+ {}
+
+ [[nodiscard]] std::uint8_t getContextId() const noexcept
+ {
+ return contextId;
+ }
+ /**
+ * @breif inform that message come from URC (eg. because call AT+CFUN=0, reset by BTS, ...)
+ * @return
+ */
+ [[nodiscard]] bool isURC()
+ {
+ return isUrc;
+ }
+};
A module-services/service-cellular/service-cellular/PacketDataTypes.hpp => module-services/service-cellular/service-cellular/PacketDataTypes.hpp +117 -0
@@ 0,0 1,117 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <string>
+#include <memory>
+
+namespace packet_data
+{
+
+ enum class DataTransfer
+ {
+ Off,
+ On
+ };
+
+ namespace APN
+ {
+ /**
+ * @brief Defines APN types
+ */
+ enum class APNType
+ {
+ Default, ///< for data traffic
+ IMS, ///< IP Multimedia Subsystem for eg VoLTE
+ MMS, ///< for MMS service
+ Fota ///< for Firmware Update
+ };
+
+ /**
+ * @brief Defines ip address type as in "TCP(IP) AT Commands Manual v1.1" from 2019-10-28
+ */
+ enum class ContextType : unsigned char
+ {
+ ipv4 = 1,
+ ipv6 = 2, ///< note, 2 was for ipv4v6 in "TCP(IP) AT Commands Manual v1.0"
+ ipv4v6 = 3
+ };
+
+ /**
+ * @brief APN Authorization method
+ */
+ enum class AuthMethod : unsigned char
+ {
+ NONE = 0,
+ PAP = 1,
+ CHAP = 2,
+ AUTO = 3,
+ };
+
+ /**
+ * @brief To configure whether to save <username> and <password> over CDMA network.
+ */
+ enum class SaveCDMAPwd : unsigned char
+ {
+ Disable = 0,
+ Enable = 1
+ };
+
+ enum class ContextState : unsigned char
+ {
+ Deactivated = 0,
+ Activated = 1
+ };
+
+ /**
+ * @brief APN configuration data
+ */
+ class Config
+ {
+ public:
+ unsigned char contextId =
+ 0; /// context on which apn is configured available values 1-16, 0 - means not set yet
+ APNType apnType = APNType::Default;
+ ContextState state = ContextState::Deactivated;
+ ContextType contextType = ContextType::ipv4; /// IP type
+ AuthMethod authMethod = AuthMethod::NONE;
+ std::string apn; /// name of the APN
+ std::string username;
+ std::string password;
+ std::string ip; /// set after connection
+
+ bool isEmpty() const noexcept
+ {
+ return apn.empty();
+ }
+
+ bool compare(std::shared_ptr<Config> c2)
+ {
+ return (this->apn == c2->apn) && (this->contextId == c2->contextId) &&
+ (this->contextType == c2->contextType) && (this->password == c2->password) &&
+ (this->username == c2->username) && (this->authMethod == c2->authMethod);
+ }
+ /**
+ * @brief Compare two APNs only by elements essential for modem
+ */
+ friend bool operator==(const Config &c1, const Config &c2)
+ {
+ return (c1.apn == c2.apn) && (c1.contextId == c2.contextId) && (c1.contextType == c2.contextType) &&
+ (c1.password == c2.password) && (c1.username == c2.username) && (c1.authMethod == c2.authMethod);
+ }
+
+ friend bool operator!=(const Config &c1, const Config &c2)
+ {
+ return !(c1 == c2);
+ }
+ };
+
+ } // namespace APN
+
+ constexpr unsigned char MINContextId = 1;
+ constexpr unsigned char MAXContextId = 16;
+ using ContextMap = std::unordered_map<unsigned char, std::shared_ptr<APN::Config>>;
+ using ContextPair = std::pair<unsigned char, std::shared_ptr<APN::Config>>;
+
+} // namespace packet_data
M module-services/service-cellular/service-cellular/ServiceCellular.hpp => module-services/service-cellular/service-cellular/ServiceCellular.hpp +29 -0
@@ 9,6 9,8 @@
#include "CellularMessage.hpp"
#include "State.hpp"
#include "USSD.hpp"
+#include "PacketData.hpp"
+#include "PacketDataCellularMessage.hpp"
#include <Modem/TS0710/DLC_channel.h>
#include <Modem/TS0710/TS0710.h>
@@ 46,6 48,11 @@ namespace sys
class Timer;
} // namespace sys
+namespace packet_data
+{
+ class PacketData;
+ class PDPContext;
+} // namespace packet_data
class ServiceCellular : public sys::Service
{
@@ 159,6 166,8 @@ class ServiceCellular : public sys::Service
void CallStateTimerHandler();
DLC_channel::Callback_t notificationCallback = nullptr;
+ std::unique_ptr<packet_data::PacketData> packetData;
+
cellular::State state;
bsp::Board board = bsp::Board::none;
@@ 221,6 230,7 @@ class ServiceCellular : public sys::Service
bool handle_fatal_failure();
bool handle_ready();
std::unique_ptr<settings::Settings> settings = std::make_unique<settings::Settings>(this);
+ bool handle_apn_conf_procedure();
bool handleAllMessagesFromMessageStorage();
[[nodiscard]] SMSRecord createSMSRecord(const UTF8 &decodedMessage,
@@ 260,8 270,27 @@ class ServiceCellular : public sys::Service
std::shared_ptr<cellular::RawCommandRespAsync> handleCellularStartOperatorsScan(
CellularStartOperatorsScanMessage *msg);
+ std::shared_ptr<CellularSetOperatorAutoSelectResponse> handleCellularSetOperatorAutoSelect(
+ CellularSetOperatorAutoSelectMessage *msg);
+ std::shared_ptr<CellularGetCurrentOperatorResponse> handleCellularGetCurrentOperator(
+ CellularGetCurrentOperatorMessage *msg);
+ std::shared_ptr<CellularGetAPNResponse> handleCellularGetAPNMessage(CellularGetAPNMessage *msg);
+ std::shared_ptr<CellularSetAPNResponse> handleCellularSetAPNMessage(CellularSetAPNMessage *msg);
+ std::shared_ptr<CellularSetOperatorResponse> handleCellularSetOperator(CellularSetOperatorMessage *msg);
+ std::shared_ptr<CellularSetDataTransferResponse> handleCellularSetDataTransferMessage(
+ CellularSetDataTransferMessage *msg);
+ std::shared_ptr<CellularGetDataTransferResponse> handleCellularGetDataTransferMessage(
+ CellularGetDataTransferMessage *msg);
+ std::shared_ptr<CellularActivateContextResponse> handleCellularActivateContextMessage(
+ CellularActivateContextMessage *msg);
+ std::shared_ptr<CellularDeactivateContextResponse> handleCellularDeactivateContextMessage(
+ CellularDeactivateContextMessage *msg);
+ std::shared_ptr<CellularGetActiveContextsResponse> handleCellularGetActiveContextsMessage(
+ CellularGetActiveContextsMessage *msg);
friend class CellularUrcHandler;
friend class SimCard;
friend class CellularRequestHandler;
friend class NetworkSettings;
+ friend class packet_data::PDPContext;
+ friend class packet_data::PacketData;
};
M module-services/service-cellular/service-cellular/State.hpp => module-services/service-cellular/service-cellular/State.hpp +1 -0
@@ 18,6 18,7 @@ namespace cellular
PowerUpInProgress, /// waiting for modem powered up by polling various bauds
CellularConfProcedure, /// configuration procedure
AudioConfigurationProcedure, /// audio configuration for modem (could be in ModemConfiguration)
+ APNConfProcedure, /// Configure APN set by user, check if modem have similar
SanityCheck, /// prior to ModemOn last sanity checks for one time configurations etc
ModemOn, /// modem ready - indicates that modem is fully configured, ( **SIM is not yet configured** )
URCReady, /// State indicates that URC handling is enabled
M module-services/service-cellular/tests/CMakeLists.txt => module-services/service-cellular/tests/CMakeLists.txt +9 -0
@@ 18,3 18,12 @@ add_catch2_executable(
module-cellular
)
+
+add_catch2_executable(
+ NAME
+ cellular-datatransfer
+ SRCS
+ unittest_datatransfer.cpp
+ LIBS
+ module-cellular
+)<
\ No newline at end of file
A module-services/service-cellular/tests/unittest_datatransfer.cpp => module-services/service-cellular/tests/unittest_datatransfer.cpp +96 -0
@@ 0,0 1,96 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#define CATCH_CONFIG_MAIN
+
+#include <catch2/catch.hpp>
+
+#include "Result.hpp"
+#include <service-cellular/PacketData.hpp>
+#include <service-cellular/PacketDataTypes.hpp>
+
+using namespace cellular;
+
+TEST_CASE("Quectel AT DataTransfer commands")
+{
+ SECTION("QIACT")
+ {
+ std::vector<std::shared_ptr<packet_data::APN::Config>> ret;
+ at::Result resp;
+
+ // OK
+ resp.code = at::Result::Code::OK;
+ resp.response.push_back("+QIACT: 1,1,1,\"10.7.157.1\"");
+ REQUIRE(resp.code == at::Result::Code::OK);
+ REQUIRE(at::response::parseQIACT(resp, ret) == true);
+ REQUIRE(ret.size() == 1);
+ REQUIRE(ret.at(0)->ip == "10.7.157.1");
+
+ ret.clear();
+ // Additional element, should be omitted, also other wrong data
+ resp.response.push_back("+QIACT:");
+ REQUIRE(at::response::parseQIACT(resp, ret) == true);
+ REQUIRE(ret.size() == 1);
+ REQUIRE(ret.at(0)->ip == "10.7.157.1");
+
+ ret.clear();
+ resp.response.clear();
+ // Empty return, should return empty list
+ REQUIRE(at::response::parseQIACT(resp, ret) == true);
+ REQUIRE(ret.size() == 0);
+
+ ret.clear();
+ resp.response.clear();
+ // Wrong should return zero
+ resp.response.push_back("+QIACT: sa sa+QIACT: fsf fsa");
+ REQUIRE(at::response::parseQIACT(resp, ret) == true);
+ REQUIRE(ret.size() == 0);
+ }
+ SECTION("QICSGP")
+ {
+ /// +QICSGP: 1,"","","",0
+ std::shared_ptr<packet_data::APN::Config> ret = std::make_shared<packet_data::APN::Config>();
+ at::Result resp;
+ resp.code = at::Result::Code::OK;
+ // OK
+ resp.response.push_back("+QICSGP: 1,\"apn\",\"\",\"\",0");
+ REQUIRE(resp.code == at::Result::Code::OK);
+ REQUIRE(at::response::parseQICSGP(resp, ret) == true);
+ REQUIRE(ret->apn == "apn");
+
+ resp.response.clear();
+ // OK
+ resp.response.push_back("+QICSGP: 1,\"apn\",\"internet\",\"password\",1");
+ REQUIRE(resp.code == at::Result::Code::OK);
+ REQUIRE(at::response::parseQICSGP(resp, ret) == true);
+ REQUIRE(ret->apn == "apn");
+ REQUIRE(ret->username == "internet");
+ REQUIRE(ret->password == "password");
+
+ resp.response.clear();
+ // empty APN
+ resp.response.push_back("+QICSGP: 1,\"\",\"\",\"\",0");
+ REQUIRE(at::response::parseQICSGP(resp, ret) == true);
+ REQUIRE(ret->apn == "");
+ REQUIRE(ret->isEmpty() == true);
+
+ // bad response
+ resp.code = at::Result::Code::ERROR;
+ REQUIRE(at::response::parseQICSGP(resp, ret) == false);
+
+ resp.response.clear();
+ // Bad token
+ resp.response.push_back("+QICSXX: 1,\"\",\"\",\"\",0");
+ REQUIRE(at::response::parseQICSGP(resp, ret) == false);
+
+ resp.response.clear();
+ // Not enought parameters
+ resp.response.push_back("+QICSGP: 1,\"\",\"\"");
+ REQUIRE(at::response::parseQICSGP(resp, ret) == false);
+
+ resp.response.clear();
+ // wrong msg
+ resp.response.push_back("ds +QICSGP: adsad +QICSGP: 1,\"\",\"");
+ REQUIRE(at::response::parseQICSGP(resp, ret) == false);
+ }
+}
M module-services/service-db/agents/settings/Settings.cpp => module-services/service-db/agents/settings/Settings.cpp +1 -1
@@ 239,4 239,4 @@ namespace settings
cbMode = nullptr;
sendMsg(std::make_shared<settings::Messages::UnregisterOnModeChange>());
}
-} // namespace Settings
+} // namespace settings
M module-services/service-eink/CMakeLists.txt => module-services/service-eink/CMakeLists.txt +2 -0
@@ 14,7 14,9 @@ message( "EINK BOARD PATH: ${CMAKE_CURRENT_LIST_DIR}/${EINK_BOARD_PATH}" )
set(SOURCES
ServiceEink.cpp
+ EinkDisplay.cpp
messages/ImageMessage.cpp
+ messages/PrepareDisplayRequest.cpp
)
add_library(${PROJECT_NAME} STATIC ${SOURCES})
M module-services/service-eink/Common.hpp => module-services/service-eink/Common.hpp +12 -3
@@ 3,7 3,16 @@
#pragma once
-namespace service::name
+namespace service
{
- constexpr char eink[] = "ServiceEink";
-};
+ namespace name
+ {
+ constexpr inline auto eink = "ServiceEink";
+ } // namespace name
+
+ namespace eink
+ {
+ constexpr inline auto DefaultScreenWidth = 480;
+ constexpr inline auto DefaultScreenHeight = 600;
+ } // namespace eink
+} // namespace service
A module-services/service-eink/EinkDisplay.cpp => module-services/service-eink/EinkDisplay.cpp +184 -0
@@ 0,0 1,184 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "EinkDisplay.hpp"
+
+#include <gui/core/Color.hpp>
+#include <gsl/gsl_util>
+
+#include <cstdio>
+#include <cstring>
+
+namespace service::eink
+{
+ namespace
+ {
+ constexpr auto DefaultSurroundingTemperature = -1000;
+ constexpr auto LutsFileName = "Luts.bin";
+ constexpr auto LUTDSize = 16385;
+ constexpr auto LUTCSize = 64;
+ constexpr auto LUTRSize = 256; ///< Needed due to \ref EINK_LUTS_FILE_PATH structure
+ constexpr auto LUTSTotalSize = LUTDSize + LUTCSize + LUTRSize;
+ constexpr auto LUTVersionInterval = 3;
+ constexpr auto LUTSubcritical = 12;
+ constexpr auto LUTCritical = 13;
+
+ EinkWaveFormSettings_t createDefaultWaveFormSettings(EinkWaveforms_e waveformMode)
+ {
+ EinkWaveFormSettings_t settings{};
+ settings.mode = waveformMode;
+ settings.temperature = DefaultSurroundingTemperature;
+ settings.LUTCData = nullptr;
+ settings.LUTCSize = 0;
+ settings.LUTDData = nullptr;
+ settings.LUTDSize = 0;
+ return settings;
+ }
+ } // namespace
+
+ EinkDisplay::EinkDisplay(::gui::Size screenSize)
+ : size{screenSize}, waveformSettings{createDefaultWaveFormSettings(EinkWaveformGC16)},
+ displayMode{EinkDisplayColorMode_e::EinkDisplayColorModeStandard}
+ {}
+
+ EinkDisplay::~EinkDisplay() noexcept
+ {
+ delete[] waveformSettings.LUTCData;
+ delete[] waveformSettings.LUTDData;
+ }
+
+ EinkStatus_e EinkDisplay::resetAndInit()
+ {
+ return EinkResetAndInitialize();
+ }
+
+ void EinkDisplay::dither()
+ {
+ EinkDitherDisplay();
+ }
+
+ void EinkDisplay::powerOn()
+ {
+ EinkPowerOn();
+ }
+
+ void EinkDisplay::powerOff()
+ {
+ EinkPowerOff();
+ }
+
+ void EinkDisplay::shutdown()
+ {
+ EinkPowerDown();
+ }
+
+ EinkStatus_e EinkDisplay::update(std::uint8_t *displayBuffer)
+ {
+ return EinkUpdateFrame(pointTopLeft.x,
+ pointTopLeft.y,
+ size.width,
+ size.height,
+ displayBuffer,
+ getCurrentBitsPerPixelFormat(),
+ displayMode);
+ }
+
+ EinkBpp_e EinkDisplay::getCurrentBitsPerPixelFormat() const noexcept
+ {
+ if (waveformSettings.mode == EinkWaveformDU2) {
+ return Eink1Bpp;
+ }
+ return Eink4Bpp;
+ }
+
+ EinkStatus_e EinkDisplay::refresh(EinkDisplayTimingsMode_e refreshMode)
+ {
+ return EinkRefreshImage(pointTopLeft.x, pointTopLeft.y, size.width, size.height, refreshMode);
+ }
+
+ bool EinkDisplay::changeWaveform(EinkWaveforms_e mode, std::int32_t temperature)
+ {
+ if (temperature == waveformSettings.temperature && mode == waveformSettings.mode) {
+ return EinkOK;
+ }
+ waveformSettings.temperature = temperature;
+ waveformSettings.mode = mode;
+
+ const auto segment = calculateWaveFormSegment(temperature);
+ auto offset = calculateWaveFormOffset(mode, segment);
+
+ auto file = std::fopen(LutsFileName, "rb");
+ if (file == nullptr) {
+ LOG_FATAL("Could not find the LUTS.bin file. Returning");
+ return false;
+ }
+ auto fileHandlerCleanup = gsl::finally([&file]() { std::fclose(file); });
+
+ resetWaveFormSettings();
+ std::fseek(file, offset, SEEK_SET);
+ std::fread(&waveformSettings.LUTDData[1], 1, LUTDSize, file);
+
+ // 0x00 - 1 frame, ... , 0x0F - 16 frames
+ const uint8_t frameCount = waveformSettings.LUTDData[1] + 1;
+ // (frameCount * 64) - size of actual LUT; (+1) - the byte containing frameCount; (+1) - EinkLUTD command
+ waveformSettings.LUTDSize = (frameCount * 64) + 1 + 1;
+
+ offset += LUTDSize;
+ std::fseek(file, offset, SEEK_SET);
+ std::fread(&waveformSettings.LUTCData[1], 1, LUTCSize, file);
+
+ EinkUpdateWaveform(&waveformSettings);
+ return true;
+ }
+
+ unsigned int EinkDisplay::calculateWaveFormSegment(std::int32_t temperature) const
+ {
+ if (temperature < 38) {
+ return temperature / LUTVersionInterval;
+ }
+ if (temperature < 43) {
+ return LUTSubcritical;
+ }
+ return LUTCritical;
+ }
+
+ unsigned int EinkDisplay::calculateWaveFormOffset(EinkWaveforms_e mode, unsigned int segment) const
+ {
+ switch (mode) {
+ case EinkWaveformINIT:
+ return LUTSTotalSize * segment;
+ case EinkWaveformA2:
+ return LUTSTotalSize * (14 + segment);
+ case EinkWaveformDU2:
+ return LUTSTotalSize * (28 + segment);
+ case EinkWaveformGLD16:
+ return LUTSTotalSize * (42 + segment);
+ case EinkWaveformGC16:
+ return LUTSTotalSize * (56 + segment);
+ }
+ throw std::invalid_argument{"Invalid waveform mode."};
+ }
+
+ void EinkDisplay::resetWaveFormSettings()
+ {
+ delete[] waveformSettings.LUTDData;
+ waveformSettings.LUTDSize = 0;
+ waveformSettings.LUTDData = new std::uint8_t[LUTDSize + 1];
+ waveformSettings.LUTDData[0] = EinkLUTD;
+
+ delete[] waveformSettings.LUTCData;
+ waveformSettings.LUTCSize = LUTCSize;
+ waveformSettings.LUTCData = new std::uint8_t[LUTCSize + 1];
+ waveformSettings.LUTCData[0] = EinkLUTC;
+ }
+
+ void EinkDisplay::setMode(EinkDisplayColorMode_e mode) noexcept
+ {
+ displayMode = mode;
+ }
+
+ ::gui::Size EinkDisplay::getSize() const noexcept
+ {
+ return size;
+ }
+} // namespace service::eink
A module-services/service-eink/EinkDisplay.hpp => module-services/service-eink/EinkDisplay.hpp +51 -0
@@ 0,0 1,51 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <gui/Common.hpp>
+
+#include <EinkIncludes.hpp>
+#include "Common.hpp"
+
+#include <cstdint>
+#include <memory>
+
+namespace service::eink
+{
+ /**
+ * Specifies the Eink display.
+ * Responsible for handling low-level Eink display operations, e.g. switching power modes, updating, refreshing,
+ * etc.
+ */
+ class EinkDisplay
+ {
+ public:
+ explicit EinkDisplay(::gui::Size screenSize = {DefaultScreenWidth, DefaultScreenHeight});
+ ~EinkDisplay() noexcept;
+
+ EinkStatus_e resetAndInit();
+ EinkStatus_e update(std::uint8_t *displayBuffer);
+ EinkStatus_e refresh(EinkDisplayTimingsMode_e refreshMode);
+ void dither();
+ void powerOn();
+ void powerOff();
+ void shutdown();
+
+ bool changeWaveform(EinkWaveforms_e mode, std::int32_t temperature);
+ void setMode(EinkDisplayColorMode_e mode) noexcept;
+
+ ::gui::Size getSize() const noexcept;
+
+ private:
+ unsigned int calculateWaveFormSegment(std::int32_t temperature) const;
+ unsigned int calculateWaveFormOffset(EinkWaveforms_e mode, unsigned int segment) const;
+ void resetWaveFormSettings();
+ EinkBpp_e getCurrentBitsPerPixelFormat() const noexcept;
+
+ static constexpr ::gui::Point pointTopLeft{0, 0};
+ const ::gui::Size size;
+ EinkWaveFormSettings_t waveformSettings;
+ EinkDisplayColorMode_e displayMode;
+ };
+} // namespace service::eink
M module-services/service-eink/ServiceEink.cpp => module-services/service-eink/ServiceEink.cpp +113 -309
@@ 3,362 3,166 @@
#include "ServiceEink.hpp"
#include "messages/EinkModeMessage.hpp"
-#include "messages/EinkDMATransfer.hpp"
-#include "messages/StateRequest.hpp"
-#include "messages/TemperatureUpdate.hpp"
+#include "messages/PrepareDisplayRequest.hpp"
#include <service-gui/Common.hpp>
-#include <service-gui/messages/GUIDisplayReady.hpp>
+#include <service-gui/messages/EinkReady.hpp>
#include <time/ScopedTime.hpp>
#include <log/log.hpp>
#include <messages/EinkMessage.hpp>
#include <messages/ImageMessage.hpp>
-#include <MessageType.hpp>
#include <Service/Bus.hpp>
-#include <service-gui/messages/GUIMessage.hpp>
-#include <cstdio>
#include <cstring>
#include <memory>
-enum class EinkWorkerCommands
+namespace service::eink
{
- Initialize,
- Initialized,
- Destroy,
- CopyImage,
- CopyCompleteCallback,
- CopyComplete
-};
-
-ServiceEink::ServiceEink(const std::string &name, std::string parent)
- : sys::Service(name, parent, 4096 + 1024),
- einkRenderBuffer(std::make_unique<uint8_t[]>(screen.height * screen.width)), selfRefereshTriggerCount{0},
- temperatureMeasurementTriggerCount{0}, powerOffTriggerCount{0},
- powerOffTimer("PwrOffTimer", this, 3000, sys::Timer::Type::SingleShot)
-{
- memset(&waveformSettings, 0, sizeof(EinkWaveFormSettings_t));
- waveformSettings.mode = EinkWaveformGC16;
- waveformSettings.temperature = -1000;
-
- connect(typeid(service::eink::EinkModeMessage), [this](sys::Message *message) -> sys::MessagePointer {
- auto msg = static_cast<service::eink::EinkModeMessage *>(message);
- this->displayMode = msg->getMode() == service::eink::EinkModeMessage::Mode::Normal
- ? EinkDisplayColorMode_e::EinkDisplayColorModeStandard
- : EinkDisplayColorMode_e::EinkDisplayColorModeInverted;
- return sys::MessageNone{};
- });
-
- connect(typeid(service::eink::EinkDMATransfer),
- [&](sys::Message *request) -> sys::MessagePointer { return handleEinkDMATransfer(request); });
-
- connect(typeid(service::eink::ImageMessage),
- [&](sys::Message *request) -> sys::MessagePointer { return handleImageMessage(request); });
-
- connect(typeid(service::eink::StateRequest),
- [&](sys::Message *request) -> sys::MessagePointer { return handleStateRequest(request); });
-
- connect(typeid(service::eink::TemperatureUpdate),
- [&](sys::Message *request) -> sys::MessagePointer { return handleTemperatureUpdate(request); });
-}
-
-ServiceEink::~ServiceEink()
-{
- if (waveformSettings.LUTCData != nullptr)
- delete[] waveformSettings.LUTCData;
- if (waveformSettings.LUTDData != nullptr)
- delete[] waveformSettings.LUTDData;
- waveformSettings.temperature = -1000;
-}
-
-sys::MessagePointer ServiceEink::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)
-{
- return std::make_shared<sys::ResponseMessage>();
-}
-
-sys::ReturnCodes ServiceEink::InitHandler()
-{
-
- LOG_INFO("[ServiceEink] Initializing");
-
- EinkStatus_e einkStatus = EinkResetAndInitialize();
-
- if (einkStatus != EinkOK) {
- LOG_FATAL("Error: Could not initialize Eink display!\n");
+ namespace
+ {
+ constexpr auto ServceEinkStackDepth = 4096 + 1024;
+ } // namespace
+
+ ServiceEink::ServiceEink(const std::string &name, std::string parent)
+ : sys::Service(name, parent, ServceEinkStackDepth), currentState{State::Running}
+ {
+ connect(typeid(EinkModeMessage),
+ [this](sys::Message *message) -> sys::MessagePointer { return handleEinkModeChangedMessage(message); });
+
+ connect(typeid(ImageMessage),
+ [this](sys::Message *request) -> sys::MessagePointer { return handleImageMessage(request); });
+
+ connect(typeid(PrepareDisplayRequest),
+ [this](sys::Message *request) -> sys::MessagePointer { return handlePrepareRequest(request); });
}
- EinkPowerOn();
-
- auto msg = std::make_shared<service::gui::GUIDisplayReady>(suspendInProgress, shutdownInProgress);
- sys::Bus::SendUnicast(msg, service::name::gui, this);
-
- return sys::ReturnCodes::Success;
-}
-
-sys::ReturnCodes ServiceEink::DeinitHandler()
-{
- EinkPowerDown();
- return sys::ReturnCodes::Success;
-}
-
-sys::ReturnCodes ServiceEink::SwitchPowerModeHandler(const sys::ServicePowerMode mode)
-{
- LOG_FATAL("[ServiceEink] PowerModeHandler: %s", c_str(mode));
-
- switch (mode) {
- case sys::ServicePowerMode ::Active: {
- suspended = false;
- EinkStatus_e einkStatus = EinkResetAndInitialize();
+ sys::MessagePointer ServiceEink::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *response)
+ {
+ return std::make_shared<sys::ResponseMessage>();
+ }
- if (einkStatus != EinkOK) {
- LOG_FATAL("Error: Could not initialize Eink display!\n");
+ sys::ReturnCodes ServiceEink::InitHandler()
+ {
+ LOG_INFO("Initializing");
+ if (const auto status = display.resetAndInit(); status != EinkOK) {
+ LOG_FATAL("Error: Could not initialize Eink display!");
+ return sys::ReturnCodes::Failure;
}
- EinkPowerOn();
- EinkPowerOff();
- } break;
- case sys::ServicePowerMode ::SuspendToRAM:
- case sys::ServicePowerMode ::SuspendToNVM:
- suspended = true;
- powerOffTimer.stop();
- EinkPowerDown();
- break;
- }
-
- return sys::ReturnCodes::Success;
-}
+ display.powerOn();
-bool ServiceEink::changeWaveform(EinkWaveforms_e mode, const int32_t temperature)
-{
+ auto msg = std::make_shared<service::gui::EinkReady>(display.getSize());
+ sys::Bus::SendUnicast(msg, service::name::gui, this);
- if ((temperature == waveformSettings.temperature) && (mode == waveformSettings.mode)) {
- return EinkOK;
+ return sys::ReturnCodes::Success;
}
- const uint32_t LUTD_SIZE = 16385;
- const uint32_t LUTC_SIZE = 64;
- const uint32_t LUTR_SIZE = 256; ///< Needed due to \ref EINK_LUTS_FILE_PATH structure
-
- const uint32_t LUTS_TOTAL_SIZE = LUTD_SIZE + LUTC_SIZE + LUTR_SIZE;
-
- waveformSettings.temperature = temperature;
- waveformSettings.mode = mode;
-
- unsigned int segment = 0;
-
- if (temperature < 38) {
- segment = temperature / 3;
+ sys::ReturnCodes ServiceEink::DeinitHandler()
+ {
+ display.shutdown();
+ return sys::ReturnCodes::Success;
}
- else {
- if (temperature < 43) {
- segment = 12;
- }
- else {
- segment = 13;
+
+ sys::ReturnCodes ServiceEink::SwitchPowerModeHandler(const sys::ServicePowerMode mode)
+ {
+ LOG_INFO("PowerModeHandler: %s", c_str(mode));
+ switch (mode) {
+ case sys::ServicePowerMode::Active:
+ enterActiveMode();
+ break;
+ case sys::ServicePowerMode::SuspendToRAM:
+ [[fallthrough]];
+ case sys::ServicePowerMode::SuspendToNVM:
+ suspend();
+ break;
}
+ return sys::ReturnCodes::Success;
}
- uint32_t offset = 0;
-
- switch (mode) {
- case EinkWaveformINIT:
- offset = LUTS_TOTAL_SIZE * segment;
- break;
-
- case EinkWaveformA2:
- offset = LUTS_TOTAL_SIZE * (14 + segment);
- break;
-
- case EinkWaveformDU2:
- offset = LUTS_TOTAL_SIZE * (28 + segment);
- break;
+ void ServiceEink::enterActiveMode()
+ {
+ setState(State::Running);
- case EinkWaveformGLD16:
- offset = LUTS_TOTAL_SIZE * (42 + segment);
- break;
-
- case EinkWaveformGC16:
- default:
- offset = LUTS_TOTAL_SIZE * (56 + segment);
- break;
+ if (const auto status = display.resetAndInit(); status != EinkOK) {
+ LOG_FATAL("Error: Could not initialize Eink display!");
+ }
+ display.powerOn();
+ display.powerOff();
}
- auto file = std::fopen("Luts.bin", "rb");
- if (file == nullptr) {
- LOG_FATAL("Could not find the LUTS.bin file. Returning");
- return false;
+ void ServiceEink::suspend()
+ {
+ setState(State::Suspended);
+ display.shutdown();
}
- if (waveformSettings.LUTDData != nullptr)
- delete[] waveformSettings.LUTDData;
-
- waveformSettings.LUTDSize = 0;
- waveformSettings.LUTDData = new uint8_t[LUTD_SIZE + 1];
-
- if (waveformSettings.LUTDData == nullptr) {
- LOG_ERROR("Could not allocate memory for the LUTD array");
- std::fclose(file);
- return false;
+ sys::MessagePointer ServiceEink::handleEinkModeChangedMessage(sys::Message *message)
+ {
+ const auto msg = static_cast<service::eink::EinkModeMessage *>(message);
+ const auto displayMode = msg->getMode() == service::eink::EinkModeMessage::Mode::Normal
+ ? EinkDisplayColorMode_e::EinkDisplayColorModeStandard
+ : EinkDisplayColorMode_e::EinkDisplayColorModeInverted;
+ display.setMode(displayMode);
+ return sys::MessageNone{};
}
- if (waveformSettings.LUTCData != nullptr)
- delete[] waveformSettings.LUTCData;
+ sys::MessagePointer ServiceEink::handleImageMessage(sys::Message *request)
+ {
+ const auto message = static_cast<service::eink::ImageMessage *>(request);
+ if (isInState(State::Suspended)) {
+ LOG_WARN("Received image while suspended, ignoring");
+ return sys::MessageNone{};
+ }
+ utils::time::Scoped measurement("ImageMessage");
- waveformSettings.LUTCSize = LUTC_SIZE;
- waveformSettings.LUTCData = new uint8_t[LUTC_SIZE + 1];
- if (waveformSettings.LUTCData == nullptr) {
- LOG_ERROR("Could not allocate memory for the LUTC array");
- std::fclose(file);
- return false;
+ updateDisplay(message->getData(), message->getRefreshMode());
+ return std::make_shared<service::eink::ImageDisplayedNotification>(message->getContextId());
}
- waveformSettings.LUTDData[0] = EinkLUTD;
- waveformSettings.LUTCData[0] = EinkLUTC;
-
- std::fseek(file, offset, SEEK_SET);
- std::fread(&waveformSettings.LUTDData[1], 1, LUTD_SIZE, file);
-
- uint8_t frameCount = waveformSettings.LUTDData[1] + 1; // 0x00 - 1 frame, ... , 0x0F - 16 frames
- waveformSettings.LUTDSize =
- frameCount * 64 + 1 +
- 1; // (frameCount * 64) - size of actual LUT; (+1) - the byte containing frameCount; (+1) - EinkLUTD command
-
- offset += LUTD_SIZE;
- std::fseek(file, offset, SEEK_SET);
- std::fread(&waveformSettings.LUTCData[1], 1, LUTC_SIZE, file);
-
- std::fclose(file);
-
- EinkUpdateWaveform(&waveformSettings);
-
- return true;
-}
-
-bool ServiceEink::deepClearScreen(int8_t temperature)
-{
- EinkWaveforms_e wv = waveformSettings.mode;
-
- EinkPowerOn();
- changeWaveform(EinkWaveforms_e::EinkWaveformA2, temperature);
-
- EinkStatus_e ret;
- memset(einkRenderBuffer.get(), 15, screen.width * screen.height);
- ret = EinkUpdateFrame(
- pointTopLeft.x, pointTopLeft.y, screen.width, screen.height, einkRenderBuffer.get(), Eink4Bpp, displayMode);
- if (ret != EinkOK)
- LOG_FATAL("Failed to update frame");
- ret = EinkRefreshImage(
- pointTopLeft.x, pointTopLeft.y, screen.width, screen.height, EinkDisplayTimingsFastRefreshMode);
- if (ret != EinkOK)
- LOG_FATAL("Failed to refresh frame");
+ void ServiceEink::updateDisplay(std::uint8_t *frameBuffer, ::gui::RefreshModes refreshMode)
+ {
+ prepareDisplay(refreshMode);
- for (uint32_t i = 0; i < 2; i++) {
- memset(einkRenderBuffer.get(), 0, screen.width * screen.height);
- ret = EinkUpdateFrame(
- pointTopLeft.x, pointTopLeft.y, screen.width, screen.height, einkRenderBuffer.get(), Eink4Bpp, displayMode);
- if (ret != EinkOK)
+ if (const auto status = display.update(frameBuffer); status != EinkOK) {
LOG_FATAL("Failed to update frame");
- ret = EinkRefreshImage(
- pointTopLeft.x, pointTopLeft.y, screen.width, screen.height, EinkDisplayTimingsFastRefreshMode);
- if (ret != EinkOK)
- LOG_FATAL("Failed to refresh frame");
+ }
- memset(einkRenderBuffer.get(), 15, screen.width * screen.height);
- ret = EinkUpdateFrame(
- pointTopLeft.x, pointTopLeft.y, screen.width, screen.height, einkRenderBuffer.get(), Eink4Bpp, displayMode);
- if (ret != EinkOK)
- LOG_FATAL("Failed to update frame");
- ret = EinkRefreshImage(
- pointTopLeft.x, pointTopLeft.y, screen.width, screen.height, EinkDisplayTimingsFastRefreshMode);
- if (ret != EinkOK)
+ const auto isDeepRefresh = refreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP;
+ if (const auto status =
+ display.refresh(isDeepRefresh ? EinkDisplayTimingsDeepCleanMode : EinkDisplayTimingsFastRefreshMode);
+ status != EinkOK) {
LOG_FATAL("Failed to refresh frame");
- }
-
- changeWaveform(wv, temperature);
-
- EinkPowerOff();
-
- return true;
-}
-
-sys::MessagePointer ServiceEink::handleEinkDMATransfer(sys::Message *message)
-{
- utils::time::Scoped scopedtimming("EinkDMATransfer");
-
- if (suspended) {
- if (suspendInProgress) {
- LOG_ERROR("drawing before suspend failed");
- suspendInProgress = false;
}
-
- LOG_INFO("[ServiceEink] Received image while suspended, ignoring");
}
- else {
- EinkPowerOn();
- int32_t temperature = EinkGetTemperatureInternal();
+ void ServiceEink::prepareDisplay(::gui::RefreshModes refreshMode)
+ {
+ display.powerOn();
- EinkStatus_e ret;
- if (deepRefresh) {
- changeWaveform(EinkWaveforms_e::EinkWaveformGC16, temperature);
- EinkDitherDisplay();
+ const auto isDeepRefresh = refreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP;
+ if (const auto temperature = EinkGetTemperatureInternal(); isDeepRefresh) {
+ display.changeWaveform(EinkWaveforms_e::EinkWaveformGC16, temperature);
+ display.dither();
}
else {
- changeWaveform(EinkWaveforms_e::EinkWaveformDU2, temperature);
+ display.changeWaveform(EinkWaveforms_e::EinkWaveformDU2, temperature);
}
-
- ret = EinkUpdateFrame(
- pointTopLeft.x, pointTopLeft.y, screen.width, screen.height, einkRenderBuffer.get(), Eink4Bpp, displayMode);
- if (ret != EinkOK)
- LOG_FATAL("Failed to update frame");
-
- if (deepRefresh) {
- ret = EinkRefreshImage(
- pointTopLeft.x, pointTopLeft.y, screen.width, screen.height, EinkDisplayTimingsDeepCleanMode);
- }
- else {
- ret = EinkRefreshImage(
- pointTopLeft.x, pointTopLeft.y, screen.width, screen.height, EinkDisplayTimingsFastRefreshMode);
- }
-
- if (ret != EinkOK)
- LOG_FATAL("Failed to refresh frame");
-
- powerOffTimer.reload();
-
- auto msg = std::make_shared<service::gui::GUIDisplayReady>(suspendInProgress, shutdownInProgress);
- suspendInProgress = false;
- shutdownInProgress = false;
- sys::Bus::SendUnicast(msg, service::name::gui, this);
}
- return nullptr;
-}
-sys::MessagePointer ServiceEink::handleImageMessage(sys::Message *request)
-{
- auto message = static_cast<service::eink::ImageMessage *>(request);
-
- powerOffTimer.stop();
- memcpy(einkRenderBuffer.get(), message->getData(), message->getSize());
- deepRefresh = message->getDeepRefresh();
-
- shutdownInProgress = message->getShutdown();
- if (shutdownInProgress)
- LOG_DEBUG("Shutdown In Progress");
-
- suspendInProgress = message->getSuspend();
- if (suspendInProgress)
- LOG_DEBUG("Suspend In Progress");
- sys::Bus::SendUnicast(std::make_shared<service::eink::EinkDMATransfer>(), GetName(), this);
- return std::make_shared<sys::ResponseMessage>();
-}
+ sys::MessagePointer ServiceEink::handlePrepareRequest(sys::Message *message)
+ {
+ const auto waveformUpdateMsg = static_cast<service::eink::PrepareDisplayRequest *>(message);
+ prepareDisplay(waveformUpdateMsg->getRefreshMode());
+ return sys::MessageNone{};
+ }
-sys::MessagePointer ServiceEink::handleStateRequest(sys::Message *)
-{
- return std::make_shared<service::gui::GUIDisplayReady>(suspendInProgress, shutdownInProgress);
-}
+ void ServiceEink::setState(State state) noexcept
+ {
+ currentState = state;
+ }
-sys::MessagePointer ServiceEink::handleTemperatureUpdate(sys::Message *)
-{
- return nullptr;
-}
+ bool ServiceEink::isInState(State state) const noexcept
+ {
+ return currentState == state;
+ }
+} // namespace service::eink
M module-services/service-eink/ServiceEink.hpp => module-services/service-eink/ServiceEink.hpp +41 -57
@@ 3,68 3,52 @@
#pragma once
-#include <gui/Common.hpp>
-#include <EinkIncludes.hpp>
#include <Service/Common.hpp>
#include <Service/Message.hpp>
#include <Service/Service.hpp>
-#include <Service/Timer.hpp>
+#include "EinkDisplay.hpp"
+
+#include <chrono>
#include <cstdint>
#include <string>
-class ServiceEink : public sys::Service
+namespace service::eink
{
- gui::Size screen = {480, 600};
- gui::Point pointTopLeft = {0, 0};
- std::unique_ptr<uint8_t[]> einkRenderBuffer;
-
- protected:
- // counts timer triggers from last self refresh
- uint32_t selfRefereshTriggerCount;
- // counts timer events from last temperature measurement
- uint32_t temperatureMeasurementTriggerCount;
- // counts trigger counts from last action that required eink to be powered on
- uint32_t powerOffTriggerCount;
-
- // number of timer triggers required to execute self refresh handler
- const uint32_t selfRefereshTriggerValue = 60;
- // number of timer triggers required to execute temperature measurement handler
- const uint32_t temperatureMeasurementTriggerValue = 5 * 60;
- // number of timer triggers from last action requiring power on eink to power down eink.
- const uint32_t powerOffTriggerValue = 3;
-
- // structure with recently loaded waveformdata
- EinkWaveFormSettings_t waveformSettings;
-
- EinkDisplayColorMode_e displayMode = EinkDisplayColorMode_e::EinkDisplayColorModeStandard;
-
- bool suspended = false;
-
- bool suspendInProgress = false;
- bool shutdownInProgress = false;
-
- bool changeWaveform(EinkWaveforms_e Mode, const int32_t temperature);
-
- bool deepClearScreen(int8_t temperature);
-
- bool deepRefresh = false;
-
- sys::ms powerOffTime = 3000;
- sys::Timer powerOffTimer;
-
- public:
- ServiceEink(const std::string &name, std::string parent = "");
- ~ServiceEink() override;
-
- sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) override;
- sys::ReturnCodes InitHandler() override;
- sys::ReturnCodes DeinitHandler() override;
- sys::ReturnCodes SwitchPowerModeHandler(const sys::ServicePowerMode mode) override final;
-
- private:
- sys::MessagePointer handleEinkDMATransfer(sys::Message *message);
- sys::MessagePointer handleImageMessage(sys::Message *message);
- sys::MessagePointer handleStateRequest(sys::Message *messge);
- sys::MessagePointer handleTemperatureUpdate(sys::Message *);
-};
+ class ServiceEink : public sys::Service
+ {
+ public:
+ explicit ServiceEink(const std::string &name, std::string parent = {});
+
+ sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *response) override;
+ sys::ReturnCodes InitHandler() override;
+ sys::ReturnCodes DeinitHandler() override;
+ sys::ReturnCodes SwitchPowerModeHandler(const sys::ServicePowerMode mode) override;
+
+ private:
+ enum class State
+ {
+ Running,
+ Suspended
+ };
+ void setState(State state) noexcept;
+ bool isInState(State state) const noexcept;
+
+ void enterActiveMode();
+ void suspend();
+ void updateDisplay(std::uint8_t *frameBuffer, ::gui::RefreshModes refreshMode);
+ void prepareDisplay(::gui::RefreshModes refreshMode);
+
+ sys::MessagePointer handleEinkModeChangedMessage(sys::Message *message);
+ sys::MessagePointer handleImageMessage(sys::Message *message);
+ sys::MessagePointer handlePrepareRequest(sys::Message *message);
+
+ EinkDisplay display;
+ State currentState;
+
+ /*
+ * PowerOffTimer to be implemented when needed.
+ * It should power off the display when not used for 3000ms.
+ */
+ };
+} // namespace service::eink
D module-services/service-eink/messages/EinkDMATransfer.hpp => module-services/service-eink/messages/EinkDMATransfer.hpp +0 -12
@@ 1,12 0,0 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#pragma once
-
-#include "EinkMessage.hpp"
-
-namespace service::eink
-{
- class EinkDMATransfer : public service::eink::EinkMessage
- {};
-}; // namespace service::eink
M module-services/service-eink/messages/EinkMessage.hpp => module-services/service-eink/messages/EinkMessage.hpp +2 -3
@@ 8,11 8,10 @@
namespace service::eink
{
-
class EinkMessage : public sys::DataMessage
{
public:
- EinkMessage() : sys::DataMessage(MessageType::EinkMessage){};
+ EinkMessage() : sys::DataMessage(MessageType::EinkMessage)
+ {}
};
-
} /* namespace seink */
M module-services/service-eink/messages/EinkModeMessage.hpp => module-services/service-eink/messages/EinkModeMessage.hpp +1 -1
@@ 16,7 16,7 @@ namespace service::eink
Invert
};
- EinkModeMessage(Mode mode) : EinkMessage(), mode(mode)
+ explicit EinkModeMessage(Mode mode) : mode(mode)
{}
[[nodiscard]] auto getMode() const noexcept
M module-services/service-eink/messages/ImageMessage.cpp => module-services/service-eink/messages/ImageMessage.cpp +24 -6
@@ 4,14 4,32 @@
#include "EinkMessage.hpp"
#include "ImageMessage.hpp"
-#include <MessageType.hpp>
-
namespace service::eink
{
+ ImageMessage::ImageMessage(int contextId, ::gui::Context *context, ::gui::RefreshModes refreshMode)
+ : contextId{contextId}, context{context}, refreshMode{refreshMode}
+ {}
+
+ auto ImageMessage::getData() noexcept -> std::uint8_t *
+ {
+ return context->getData();
+ }
- ImageMessage::ImageMessage(
- uint32_t x, uint32_t y, uint32_t w, uint32_t h, bool deepRefresh, uint8_t *data, bool suspend, bool shutdown)
- : EinkMessage(), x{x}, y{y}, w{w}, h{h}, deepRefresh{deepRefresh}, data{data}, suspend{suspend}, shutdown{
- shutdown}
+ auto ImageMessage::getRefreshMode() const noexcept -> ::gui::RefreshModes
+ {
+ return refreshMode;
+ }
+
+ auto ImageMessage::getContextId() const noexcept -> int
+ {
+ return contextId;
+ }
+
+ ImageDisplayedNotification::ImageDisplayedNotification(int contextId) : contextId{contextId}
{}
+
+ auto ImageDisplayedNotification::getContextId() const noexcept -> int
+ {
+ return contextId;
+ }
} /* namespace seink */
M module-services/service-eink/messages/ImageMessage.hpp => module-services/service-eink/messages/ImageMessage.hpp +22 -37
@@ 6,49 6,34 @@
#include "EinkMessage.hpp"
#include <cstdint>
+#include <module-gui/gui/core/Context.hpp>
+#include <module-gui/gui/Common.hpp>
namespace service::eink
{
-
class ImageMessage : public EinkMessage
{
- protected:
- uint32_t x, y, w, h;
- bool deepRefresh;
- uint8_t *data;
- bool suspend = false;
- bool shutdown = false;
-
public:
- ImageMessage(uint32_t x,
- uint32_t y,
- uint32_t w,
- uint32_t h,
- bool deepRefresh,
- uint8_t *data,
- bool suspend,
- bool shutdown);
-
- uint8_t *getData()
- {
- return data;
- };
- uint32_t getSize()
- {
- return w * h;
- };
- bool getDeepRefresh()
- {
- return deepRefresh;
- };
- bool getSuspend()
- {
- return suspend;
- };
- bool getShutdown()
- {
- return shutdown;
- };
+ ImageMessage(int contextId, ::gui::Context *context, ::gui::RefreshModes refreshMode);
+
+ [[nodiscard]] auto getContextId() const noexcept -> int;
+ [[nodiscard]] auto getData() noexcept -> std::uint8_t *;
+ [[nodiscard]] auto getRefreshMode() const noexcept -> ::gui::RefreshModes;
+
+ private:
+ int contextId;
+ ::gui::Context *context;
+ ::gui::RefreshModes refreshMode;
};
+ class ImageDisplayedNotification : public EinkMessage
+ {
+ public:
+ explicit ImageDisplayedNotification(int contextId);
+
+ [[nodiscard]] auto getContextId() const noexcept -> int;
+
+ private:
+ int contextId;
+ };
} /* namespace seink */
A module-services/service-eink/messages/PrepareDisplayRequest.cpp => module-services/service-eink/messages/PrepareDisplayRequest.cpp +15 -0
@@ 0,0 1,15 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "PrepareDisplayRequest.hpp"
+
+namespace service::eink
+{
+ PrepareDisplayRequest::PrepareDisplayRequest(::gui::RefreshModes refreshMode) : refreshMode{refreshMode}
+ {}
+
+ auto PrepareDisplayRequest::getRefreshMode() const noexcept -> ::gui::RefreshModes
+ {
+ return refreshMode;
+ }
+} // namespace service::eink
A module-services/service-eink/messages/PrepareDisplayRequest.hpp => module-services/service-eink/messages/PrepareDisplayRequest.hpp +22 -0
@@ 0,0 1,22 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "EinkMessage.hpp"
+
+#include <gui/Common.hpp>
+
+namespace service::eink
+{
+ class PrepareDisplayRequest : public EinkMessage
+ {
+ public:
+ explicit PrepareDisplayRequest(::gui::RefreshModes refreshMode);
+
+ [[nodiscard]] auto getRefreshMode() const noexcept -> ::gui::RefreshModes;
+
+ private:
+ ::gui::RefreshModes refreshMode;
+ };
+} // namespace service::eink
D module-services/service-eink/messages/StateRequest.hpp => module-services/service-eink/messages/StateRequest.hpp +0 -12
@@ 1,12 0,0 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#pragma once
-
-#include "EinkMessage.hpp"
-
-namespace service::eink
-{
- class StateRequest : public EinkMessage
- {};
-} // namespace service::eink
D module-services/service-eink/messages/TemperatureUpdate.hpp => module-services/service-eink/messages/TemperatureUpdate.hpp +0 -12
@@ 1,12 0,0 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
-// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
-
-#pragma once
-
-#include "EinkMessage.hpp"
-
-namespace service::eink
-{
- class TemperatureUpdate : public EinkMessage
- {};
-} // namespace service::eink
M module-services/service-evtmgr/EventManager.cpp => module-services/service-evtmgr/EventManager.cpp +1 -3
@@ 123,9 123,7 @@ sys::MessagePointer EventManager::DataReceivedHandler(sys::DataMessage *msgl, sy
else if (msgl->messageType == MessageType::EVMBatteryLevel && msgl->sender == this->GetName()) {
auto *msg = static_cast<sevm::BatteryLevelMessage *>(msgl);
- auto message = std::make_shared<sevm::BatteryLevelMessage>();
- message->levelPercents = msg->levelPercents;
- message->fullyCharged = msg->fullyCharged;
+ auto message = std::make_shared<sevm::BatteryLevelMessage>(msg->levelPercents, msg->fullyCharged);
if (!targetApplication.empty()) {
sys::Bus::SendUnicast(message, targetApplication, this);
M module-services/service-evtmgr/WorkerEvent.cpp => module-services/service-evtmgr/WorkerEvent.cpp +13 -7
@@ 92,14 92,20 @@ bool WorkerEvent::handleMessage(uint32_t queueID)
return false;
}
if (notification == static_cast<uint8_t>(bsp::batteryIRQSource::INTB)) {
- uint8_t battLevel = 0;
- bsp::battery_getBatteryLevel(battLevel);
+ LOG_DEBUG("Battery INTB");
+ const auto status = bsp::battery_getStatusRegister();
+ if (status & static_cast<std::uint16_t>(bsp::batteryINTBSource::minVAlert)) {
+ auto messageBrownout = std::make_shared<sevm::BatteryBrownoutMessage>();
+ sys::Bus::SendUnicast(messageBrownout, service::name::system_manager, this->service);
+ }
+ if (status & static_cast<std::uint16_t>(bsp::batteryINTBSource::SOCOnePercentChange)) {
+ std::uint8_t battLevel = 0;
+ bsp::battery_getBatteryLevel(battLevel);
+ auto message = std::make_shared<sevm::BatteryLevelMessage>(battLevel, false);
+ sys::Bus::SendUnicast(message, service::name::evt_manager, this->service);
+ battery_level_check::checkBatteryLevelCritical();
+ }
bsp::battery_ClearAllIRQs();
- auto message = std::make_shared<sevm::BatteryLevelMessage>();
- message->levelPercents = battLevel;
- message->fullyCharged = false;
- sys::Bus::SendUnicast(message, service::name::evt_manager, this->service);
- battery_level_check::checkBatteryLevelCritical();
}
if (notification == static_cast<uint8_t>(bsp::batteryIRQSource::INOKB)) {
bool status;
M module-services/service-evtmgr/service-evtmgr/BatteryMessages.hpp => module-services/service-evtmgr/service-evtmgr/BatteryMessages.hpp +10 -1
@@ 12,7 12,8 @@ namespace sevm
class BatteryLevelMessage : public Message
{
public:
- BatteryLevelMessage() : Message(MessageType::EVMBatteryLevel)
+ BatteryLevelMessage(uint8_t levelPercents, bool fullyCharged)
+ : Message(MessageType::EVMBatteryLevel), levelPercents(levelPercents), fullyCharged(fullyCharged)
{
type = Type::Data;
}
@@ 44,4 45,12 @@ namespace sevm
BatteryLevelCriticalMessage() : Message(MessageType::EVMBatteryLevelCritical)
{}
};
+
+ class BatteryBrownoutMessage : public Message
+ {
+ public:
+ BatteryBrownoutMessage() : Message(MessageType::EVMBatteryBrownout)
+ {}
+ };
+
} // namespace sevm
M module-services/service-fota/FotaUrcHandler.hpp => module-services/service-fota/FotaUrcHandler.hpp +1 -0
@@ 26,6 26,7 @@ class FotaUrcHandler : public UrcHandler
virtual void Handle(Cusd &urc){};
virtual void Handle(Ctze &urc){};
virtual void Handle(Cpin &urc){};
+ virtual void Handle(Qiurc &urc){};
virtual void Handle(PoweredDown &urc){};
virtual void Handle(UrcResponse &urc){};
M module-services/service-gui/CMakeLists.txt => module-services/service-gui/CMakeLists.txt +7 -2
@@ 4,17 4,19 @@ message( "${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}" )
set(SOURCES
"${CMAKE_CURRENT_LIST_DIR}/ServiceGUI.cpp"
"${CMAKE_CURRENT_LIST_DIR}/WorkerGUI.cpp"
+ "${CMAKE_CURRENT_LIST_DIR}/ContextPool.cpp"
+ "${CMAKE_CURRENT_LIST_DIR}/DrawCommandsQueue.cpp"
+ "${CMAKE_CURRENT_LIST_DIR}/SynchronizationMechanism.cpp"
"${CMAKE_CURRENT_LIST_DIR}/messages/DrawMessage.cpp"
)
-
add_library(${PROJECT_NAME} STATIC ${SOURCES})
target_link_libraries(${PROJECT_NAME}
service-eink
module-gui
module-apps
- )
+)
target_include_directories(${PROJECT_NAME}
PUBLIC
@@ 22,3 24,6 @@ target_include_directories(${PROJECT_NAME}
"${CMAKE_CURRENT_LIST_DIR}/messages"
)
+if (${ENABLE_TESTS})
+ add_subdirectory(tests)
+endif()
M module-services/service-gui/Common.hpp => module-services/service-gui/Common.hpp +1 -1
@@ 6,4 6,4 @@
namespace service::name
{
constexpr inline auto gui = "ServiceGUI";
-}
+} // namespace service::name
A module-services/service-gui/ContextPool.cpp => module-services/service-gui/ContextPool.cpp +109 -0
@@ 0,0 1,109 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "ContextPool.hpp"
+
+#include <mutex.hpp>
+
+#include <algorithm>
+
+namespace service::gui
+{
+ using namespace std::literals::chrono_literals;
+
+ namespace
+ {
+ std::unique_ptr<::gui::Context> allocateContext(::gui::Size screenSize)
+ {
+ return std::make_unique<::gui::Context>(screenSize.width, screenSize.height);
+ }
+
+ constexpr auto DefaultWaitTime = 5000ms;
+ } // namespace
+
+ ContextPool::ContextPool(::gui::Size screenSize,
+ std::size_t capacity,
+ std::unique_ptr<SynchronizationMechanism> &&synchronization)
+ : synchronization{std::move(synchronization)}
+ {
+ contexts.reserve(capacity);
+ freeContextIds.reserve(capacity);
+ lockedContextIds.reserve(capacity);
+
+ for (std::size_t i = 0; i < capacity; ++i) {
+ contexts.push_back(allocateContext(screenSize));
+ freeContextIds.push_back(i);
+ }
+ }
+
+ auto ContextPool::peekContext(int id) noexcept -> ::gui::Context *
+ {
+ return contexts[id].get();
+ }
+
+ auto ContextPool::borrowContext() -> std::pair<int, ::gui::Context *>
+ {
+ cpp_freertos::LockGuard lock(mutex);
+ synchronization->wait(mutex, DefaultWaitTime, [this]() { return isAvailable(); });
+
+ const auto contextId = freeContextIds.front();
+ lockContext(contextId);
+ return std::make_pair(contextId, contexts[contextId].get());
+ }
+
+ auto ContextPool::borrowContext(int id) -> ::gui::Context *
+ {
+ cpp_freertos::LockGuard lock(mutex);
+ const auto it = std::find(freeContextIds.begin(), freeContextIds.end(), id);
+ if (it == freeContextIds.end()) {
+ return nullptr;
+ }
+
+ lockContext(id);
+ return contexts[id].get();
+ }
+
+ auto ContextPool::isAnyContextLocked() const -> bool
+ {
+ cpp_freertos::LockGuard lock(mutex);
+ return !lockedContextIds.empty();
+ }
+
+ auto ContextPool::isAvailable() const noexcept -> bool
+ {
+ return !freeContextIds.empty();
+ }
+
+ void ContextPool::lockContext(int contextId)
+ {
+ removeContextId(freeContextIds, contextId);
+ addContextId(lockedContextIds, contextId);
+ }
+
+ void ContextPool::returnContext(int id)
+ {
+ cpp_freertos::LockGuard lock(mutex);
+ freeContext(id);
+
+ synchronization->notify();
+ }
+
+ void ContextPool::freeContext(int contextId)
+ {
+ removeContextId(lockedContextIds, contextId);
+ addContextId(freeContextIds, contextId);
+ }
+
+ void ContextPool::removeContextId(std::vector<int> &sequence, int contextId)
+ {
+ sequence.erase(std::remove(sequence.begin(), sequence.end(), contextId), sequence.end());
+ }
+
+ void ContextPool::addContextId(std::vector<int> &sequence, int contextId)
+ {
+ if (const auto it = std::find(sequence.begin(), sequence.end(), contextId); it == sequence.end()) {
+ // Add if not found.
+ sequence.push_back(contextId);
+ }
+ }
+} // namespace service::gui
A module-services/service-gui/ContextPool.hpp => module-services/service-gui/ContextPool.hpp +74 -0
@@ 0,0 1,74 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "SynchronizationMechanism.hpp"
+
+#include <gui/Common.hpp>
+#include <gui/core/Context.hpp>
+
+#include <mutex.hpp>
+
+#include <chrono>
+#include <functional>
+#include <memory>
+#include <utility>
+#include <vector>
+
+namespace service::gui
+{
+ class ContextPool
+ {
+ public:
+ ContextPool(
+ ::gui::Size screenSize,
+ std::size_t capacity,
+ std::unique_ptr<SynchronizationMechanism> &&synchronization = getFreeRtosSynchronizationMechanism());
+
+ /**
+ * Peeks the context under specific id
+ * @param id Context id
+ * @return Context data
+ */
+ [[nodiscard]] auto peekContext(int id) noexcept -> ::gui::Context *;
+ /**
+ * Locks the first available context
+ * @return A context and its id
+ */
+ [[nodiscard]] auto borrowContext() -> std::pair<int, ::gui::Context *>;
+ /**
+ * Locks the context
+ * @param id Context id
+ * @return Context data
+ */
+ [[nodiscard]] auto borrowContext(int id) -> ::gui::Context *;
+ /**
+ * Checks whether any context is currently locked
+ * @return True if any context is locked, false otherwise
+ */
+ [[nodiscard]] auto isAnyContextLocked() const -> bool;
+ /**
+ * Makes a context (specified by its id) available again.
+ * @param id Context id
+ */
+ void returnContext(int id);
+
+ private:
+ using ContextIdContainer = std::vector<int>;
+
+ [[nodiscard]] auto isAvailable() const noexcept -> bool;
+
+ void freeContext(int contextId);
+ void lockContext(int contextId);
+ static void removeContextId(std::vector<int> &sequence, int contextId);
+ static void addContextId(std::vector<int> &sequence, int contextId);
+
+ std::vector<std::unique_ptr<::gui::Context>> contexts;
+ ContextIdContainer lockedContextIds;
+ ContextIdContainer freeContextIds;
+
+ mutable cpp_freertos::MutexStandard mutex;
+ std::unique_ptr<SynchronizationMechanism> synchronization;
+ };
+} // namespace service::gui
A module-services/service-gui/DrawCommandsQueue.cpp => module-services/service-gui/DrawCommandsQueue.cpp +62 -0
@@ 0,0 1,62 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "DrawCommandsQueue.hpp"
+
+namespace service::gui
+{
+ namespace
+ {
+ constexpr std::chrono::milliseconds WaitTimeout{5000};
+ } // namespace
+
+ DrawCommandsQueue::DrawCommandsQueue(std::size_t expectedSize,
+ std::unique_ptr<SynchronizationMechanism> &&synchronization)
+ : synchronization{std::move(synchronization)}
+ {
+ queue.reserve(expectedSize);
+ }
+
+ void DrawCommandsQueue::enqueue(QueueItem &&item)
+ {
+ cpp_freertos::LockGuard lock{queueMutex};
+ queue.push_back(std::move(item));
+
+ synchronization->notify();
+ }
+
+ auto DrawCommandsQueue::dequeue() -> QueueItem
+ {
+ cpp_freertos::LockGuard lock{queueMutex};
+ synchronization->wait(queueMutex, WaitTimeout, [this]() { return !queue.empty(); });
+
+ auto item = std::move(queue.front());
+ queue.erase(queue.begin());
+ return item;
+ }
+
+ auto DrawCommandsQueue::getMaxRefreshModeAndClear() -> ::gui::RefreshModes
+ {
+ cpp_freertos::LockGuard lock{queueMutex};
+ const auto deepRefreshRequested = std::any_of(queue.begin(), queue.end(), [](const auto &item) {
+ return item.refreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP;
+ });
+ const auto maxRefreshMode =
+ deepRefreshRequested ? ::gui::RefreshModes::GUI_REFRESH_DEEP : ::gui::RefreshModes::GUI_REFRESH_FAST;
+
+ queue.clear();
+ return maxRefreshMode;
+ }
+
+ void DrawCommandsQueue::clear()
+ {
+ cpp_freertos::LockGuard lock{queueMutex};
+ queue.clear();
+ }
+
+ auto DrawCommandsQueue::size() const noexcept -> QueueContainer::size_type
+ {
+ cpp_freertos::LockGuard lock{queueMutex};
+ return queue.size();
+ }
+} // namespace service::gui
A module-services/service-gui/DrawCommandsQueue.hpp => module-services/service-gui/DrawCommandsQueue.hpp +44 -0
@@ 0,0 1,44 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include "SynchronizationMechanism.hpp"
+
+#include <gui/core/DrawCommand.hpp>
+
+#include <cstdint>
+#include <list>
+#include <memory>
+#include <vector>
+
+namespace service::gui
+{
+ class DrawCommandsQueue
+ {
+ public:
+ using CommandList = std::list<std::unique_ptr<::gui::DrawCommand>>;
+ struct QueueItem
+ {
+ CommandList commands;
+ ::gui::RefreshModes refreshMode = ::gui::RefreshModes::GUI_REFRESH_FAST;
+ };
+ using QueueContainer = std::vector<QueueItem>;
+
+ explicit DrawCommandsQueue(
+ std::size_t expectedSize,
+ std::unique_ptr<SynchronizationMechanism> &&synchronization = getFreeRtosSynchronizationMechanism());
+
+ void enqueue(QueueItem &&item);
+ [[nodiscard]] auto dequeue() -> QueueItem;
+ [[nodiscard]] auto getMaxRefreshModeAndClear() -> ::gui::RefreshModes;
+ void clear();
+ [[nodiscard]] auto size() const noexcept -> QueueContainer::size_type;
+
+ private:
+ QueueContainer queue;
+
+ mutable cpp_freertos::MutexStandard queueMutex;
+ std::unique_ptr<SynchronizationMechanism> synchronization;
+ };
+} // namespace service::gui
M module-services/service-gui/ServiceGUI.cpp => module-services/service-gui/ServiceGUI.cpp +167 -166
@@ 4,245 4,246 @@
#include "ServiceGUI.hpp"
#include "WorkerGUI.hpp"
-#include "messages/GUIMessage.hpp"
#include "messages/DrawMessage.hpp"
-#include "messages/RenderingFinished.hpp"
-#include "messages/GUIDisplayReady.hpp"
+#include "messages/EinkReady.hpp"
#include <DrawCommand.hpp>
#include <FontManager.hpp>
-#include <MessageType.hpp>
-#include <gui/core/Context.hpp>
#include <gui/core/ImageManager.hpp>
#include <log/log.hpp>
-#include <projdefs.h>
-#include <service-appmgr/Controller.hpp>
+#include <service-eink/Common.hpp>
#include <service-eink/messages/ImageMessage.hpp>
#include <service-eink/messages/EinkMessage.hpp>
-#include <service-eink/messages/StateRequest.hpp>
-#include <Service/Bus.hpp>
-#include <Service/Worker.hpp>
+#include <service-eink/messages/PrepareDisplayRequest.hpp>
#include <SystemManager/SystemManager.hpp>
-#include <service-eink/Common.hpp>
-#include <task.h>
-
-extern "C"
-{
-#include <FreeRTOS.h>
-#include <semphr.h>
-}
-
-#include <cstddef>
-#include <list>
-#include <memory>
-#include <utility>
+#include <gsl/gsl_util>
#include <purefs/filesystem_paths.hpp>
-namespace sgui
+namespace service::gui
{
- ServiceGUI::ServiceGUI(const std::string &name, std::string parent, uint32_t screenWidth, uint32_t screenHeight)
- : sys::Service(name, parent, 4096, sys::ServicePriority::Idle), renderContext{nullptr},
- transferContext{nullptr}, renderFrameCounter{1}, transferedFrameCounter{0}, screenWidth{screenWidth},
- screenHeight{screenHeight}, semCommands{NULL}
+ namespace
{
-
- LOG_INFO("[ServiceGUI] Initializing");
-
- renderContext = new gui::Context(screenWidth, screenHeight);
- transferContext = new gui::Context(screenWidth, screenHeight);
-
- gui::FontManager &fontManager = gui::FontManager::getInstance();
- fontManager.init(purefs::dir::getCurrentOSPath() / "assets");
-
- gui::ImageManager &imageManager = gui::ImageManager::getInstance();
- imageManager.init(purefs::dir::getCurrentOSPath() / "assets");
-
- connect(typeid(sgui::DrawMessage),
- [&](sys::Message *request) -> sys::MessagePointer { return handleDrawMessage(request); });
-
- connect(typeid(service::gui::RenderingFinished),
- [&](sys::Message *request) -> sys::MessagePointer { return handleGUIRenderingFinished(request); });
-
- connect(typeid(service::gui::GUIDisplayReady),
- [&](sys::Message *request) -> sys::MessagePointer { return handleGUIDisplayReady(request); });
+ constexpr auto ServiceGuiStackDepth = 4096U;
+ constexpr auto ContextsCount = 2;
+ constexpr auto CommandsQueueCapacity = 3;
+ constexpr std::chrono::milliseconds ContextReleaseTimeout{1000};
+ } // namespace
+
+ ServiceGUI::ServiceGUI(const std::string &name, std::string parent)
+ : sys::Service(name, parent, ServiceGuiStackDepth), commandsQueue{std::make_unique<DrawCommandsQueue>(
+ CommandsQueueCapacity)},
+ contextReleaseTimer{
+ std::make_unique<sys::Timer>(this, ContextReleaseTimeout.count(), sys::Timer::Type::SingleShot)},
+ currentState{State::NotInitialised}
+ {
+ initAssetManagers();
+ registerMessageHandlers();
}
- ServiceGUI::~ServiceGUI()
+ ServiceGUI::~ServiceGUI() noexcept = default;
+
+ void ServiceGUI::initAssetManagers()
{
- LOG_INFO("[ServiceGUI] Cleaning resources");
- if (renderContext)
- delete renderContext;
- if (transferContext)
- delete transferContext;
+ const auto assetsPath = purefs::dir::getCurrentOSPath() / "assets";
+ ::gui::FontManager::getInstance().init(assetsPath);
+ ::gui::ImageManager::getInstance().init(assetsPath);
}
- void ServiceGUI::sendBuffer()
+ void ServiceGUI::registerMessageHandlers()
{
- transferContext->insert(0, 0, renderContext);
+ connect(typeid(EinkReady),
+ [this](sys::Message *request) -> sys::MessagePointer { return handleEinkReady(request); });
- auto msg =
- std::make_shared<service::eink::ImageMessage>(0,
- 0,
- transferContext->getW(),
- transferContext->getH(),
- (mode == gui::RefreshModes::GUI_REFRESH_DEEP ? true : false),
- transferContext->getData(),
- suspendInProgress,
- shutdownInProgress);
- einkReady = false;
- auto ret = sys::Bus::SendUnicast(msg, service::name::eink, this, 2000);
- if (ret.first == sys::ReturnCodes::Success) {
- transferedFrameCounter = renderFrameCounter;
- }
- mode = gui::RefreshModes::GUI_REFRESH_FAST;
- }
+ connect(typeid(DrawMessage),
+ [this](sys::Message *request) -> sys::MessagePointer { return handleDrawMessage(request); });
- void ServiceGUI::sendToRender()
- {
- rendering = true;
- worker->send(static_cast<uint32_t>(WorkerGUICommands::Render), NULL);
+ connect(typeid(RenderingFinished),
+ [this](sys::Message *request) -> sys::MessagePointer { return handleGUIRenderingFinished(request); });
+
+ connect(typeid(eink::ImageDisplayedNotification), [this](sys::Message *request) -> sys::MessagePointer {
+ return handleImageDisplayedNotification(request);
+ });
}
sys::MessagePointer ServiceGUI::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)
{
- return std::make_shared<sys::ResponseMessage>();
+ return sys::MessageNone{};
}
sys::ReturnCodes ServiceGUI::InitHandler()
{
- semCommands = xSemaphoreCreateBinary();
- if (semCommands == NULL) {
- LOG_FATAL("Failed to create commands semaphore.");
- return sys::ReturnCodes::Failure;
- }
- xSemaphoreGive(semCommands);
-
+ std::list<sys::WorkerQueueInfo> queueInfo{
+ {WorkerGUI::SignallingQueueName, WorkerGUI::SignalSize, WorkerGUI::SignallingQueueCapacity}};
worker = std::make_unique<WorkerGUI>(this);
- std::list<sys::WorkerQueueInfo> list;
- worker->init(list);
+ worker->init(queueInfo);
worker->run();
-
- if (einkReady == false) {
- requestSent = true;
- sys::Bus::SendUnicast(std::make_shared<service::eink::StateRequest>(), service::name::eink, this);
- }
return sys::ReturnCodes::Success;
}
sys::ReturnCodes ServiceGUI::DeinitHandler()
{
-
- if (semCommands != NULL)
- vSemaphoreDelete(semCommands);
- semCommands = NULL;
-
- worker->stop();
- worker->join();
- worker->deinit();
-
+ worker->close();
return sys::ReturnCodes::Success;
}
sys::ReturnCodes ServiceGUI::SwitchPowerModeHandler(const sys::ServicePowerMode mode)
{
- LOG_FATAL("[ServiceGUI] PowerModeHandler: %s", c_str(mode));
-
+ LOG_INFO("PowerModeHandler: %s", c_str(mode));
switch (mode) {
- case sys::ServicePowerMode ::Active:
+ case sys::ServicePowerMode::Active:
+ setState(contextPool != nullptr ? State::Idle : State::NotInitialised);
break;
- case sys::ServicePowerMode ::SuspendToRAM:
- case sys::ServicePowerMode ::SuspendToNVM:
+ case sys::ServicePowerMode::SuspendToRAM:
+ [[fallthrough]];
+ case sys::ServicePowerMode::SuspendToNVM:
+ setState(State::Suspended);
break;
}
-
return sys::ReturnCodes::Success;
}
sys::MessagePointer ServiceGUI::handleDrawMessage(sys::Message *message)
{
- auto dmsg = static_cast<sgui::DrawMessage *>(message);
- if (!dmsg->commands.empty()) {
-
- if (!suspendInProgress) {
+ if (isInState(State::NotInitialised)) {
+ LOG_WARN("Service not yet initialised - ignoring draw commands");
+ return sys::MessageNone{};
+ }
+ if (isInState(State::Suspended)) {
+ LOG_WARN("Suspended - ignoring draw commands");
+ return sys::MessageNone{};
+ }
- if (dmsg->isType(sgui::DrawMessage::Type::SHUTDOWN)) {
- LOG_WARN("Shutdown - received shutdown draw commands");
- shutdownInProgress = true;
- }
+ if (const auto drawMsg = static_cast<DrawMessage *>(message); !drawMsg->commands.empty()) {
+ if (drawMsg->isType(DrawMessage::Type::SHUTDOWN) || drawMsg->isType(DrawMessage::Type::SUSPEND)) {
+ setState(State::Suspended);
+ }
- if (dmsg->isType(sgui::DrawMessage::Type::SUSPEND)) {
- LOG_WARN("Suspended - received suspend draw commands");
- suspendInProgress = true;
- }
+ prepareDisplay(drawMsg->mode);
+ notifyRenderer(std::move(drawMsg->commands), drawMsg->mode);
+ }
+ return sys::MessageNone{};
+ }
- if (dmsg->mode == gui::RefreshModes::GUI_REFRESH_DEEP) {
- mode = dmsg->mode;
- }
+ void ServiceGUI::prepareDisplay(::gui::RefreshModes refreshMode)
+ {
+ auto msg = std::make_shared<service::eink::PrepareDisplayRequest>(refreshMode);
+ sys::Bus::SendUnicast(msg, service::name::eink, this);
+ }
- if (xSemaphoreTake(semCommands, pdMS_TO_TICKS(1000)) == pdTRUE) {
- commands = std::move(dmsg->commands);
- xSemaphoreGive(semCommands);
- }
- else {
- LOG_ERROR("Failed to acquire semaphore");
- }
+ void ServiceGUI::notifyRenderer(std::list<std::unique_ptr<::gui::DrawCommand>> &&commands,
+ ::gui::RefreshModes refreshMode)
+ {
+ enqueueDrawCommands(DrawCommandsQueue::QueueItem{std::move(commands), refreshMode});
+ worker->notify(WorkerGUI::Signal::Render);
+ }
- if (!rendering) {
- sendToRender();
- }
- }
- else {
- LOG_WARN("Suspended - ignoring draw commands");
- }
+ void ServiceGUI::enqueueDrawCommands(DrawCommandsQueue::QueueItem &&item)
+ {
+ // Clear all queue elements for now to keep only the latest command in the queue.
+ // In the future, we'll need to implement more sophisticated algorithm for partially refresh the display.
+ if (item.refreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP) {
+ commandsQueue->clear();
+ }
+ else if (const auto maxRefreshMode = commandsQueue->getMaxRefreshModeAndClear();
+ maxRefreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP) {
+ item.refreshMode = maxRefreshMode;
}
- return nullptr;
+ commandsQueue->enqueue(std::move(item)); // 3 consecutive deep refreshes after leaving messages? :/
}
sys::MessagePointer ServiceGUI::handleGUIRenderingFinished(sys::Message *message)
{
- rendering = false;
- renderFrameCounter++;
-
- if (einkReady) {
- sendBuffer();
+ auto finishedMsg = static_cast<service::gui::RenderingFinished *>(message);
+ const auto contextId = finishedMsg->getContextId();
+ const auto refreshMode = finishedMsg->getRefreshMode();
+ if (isInState(State::Idle)) {
+ const auto context = contextPool->peekContext(contextId);
+ sendOnDisplay(context, contextId, refreshMode);
+ invalidateCache();
}
- else if (!requestSent) {
- requestSent = true;
- sys::Bus::SendUnicast(std::make_shared<service::eink::StateRequest>(), service::name::eink, this);
+ else {
+ cacheRender(contextId, refreshMode);
+ contextPool->returnContext(contextId);
}
- return nullptr;
+ return sys::MessageNone{};
+ }
+
+ void ServiceGUI::sendOnDisplay(::gui::Context *context, int contextId, ::gui::RefreshModes refreshMode)
+ {
+ setState(State::Busy);
+ auto imageMsg = std::make_shared<service::eink::ImageMessage>(contextId, context, refreshMode);
+ sys::Bus::SendUnicast(imageMsg, service::name::eink, this);
+ scheduleContextRelease(contextId);
}
- sys::MessagePointer ServiceGUI::handleGUIDisplayReady(sys::Message *message)
+ void ServiceGUI::scheduleContextRelease(int contextId)
{
- auto msg = static_cast<service::gui::GUIDisplayReady *>(message);
- einkReady = true;
- requestSent = false;
+ // Whenever the response from ServiceEink doesn't come, the context has to be released automatically after a
+ // timeout.
+ contextReleaseTimer->connect([this, contextId](sys::Timer &it) {
+ eink::ImageDisplayedNotification notification{contextId};
+ handleImageDisplayedNotification(¬ification);
+ LOG_WARN("Context # %d released after timeout. Does ServiceEink respond properly?", contextId);
+ });
+ contextReleaseTimer->start();
+ }
- if (msg->getShutdownInProgress()) {
- einkReady = false;
- suspendInProgress = false;
- LOG_DEBUG("last rendering before shutdown finished.");
+ void ServiceGUI::cacheRender(int contextId, ::gui::RefreshModes refreshMode)
+ {
+ cachedRender = CachedRender{contextId, refreshMode};
+ }
- sys::SystemManager::CloseSystem(this);
- }
+ void ServiceGUI::invalidateCache()
+ {
+ cachedRender = std::nullopt;
+ }
- if (msg->getSuspendInProgress()) {
- einkReady = false;
- suspendInProgress = false;
- LOG_DEBUG("last rendering before suspend is finished.");
+ sys::MessagePointer ServiceGUI::handleEinkReady(sys::Message *message)
+ {
+ const auto msg = static_cast<service::gui::EinkReady *>(message);
+ contextPool = std::make_unique<ContextPool>(msg->getDisplaySize(), ContextsCount);
+ setState(State::Idle);
+ return sys::MessageNone{};
+ }
- app::manager::Controller::changePowerSaveMode(this);
- }
- if ((renderFrameCounter != transferedFrameCounter) && (!rendering)) {
- sendBuffer();
+ sys::MessagePointer ServiceGUI::handleImageDisplayedNotification(sys::Message *message)
+ {
+ const auto msg = static_cast<eink::ImageDisplayedNotification *>(message);
+ const auto contextId = msg->getContextId();
+ contextPool->returnContext(contextId);
+ contextReleaseTimer->stop();
+ setState(State::Idle);
+
+ if (isNextFrameReady()) {
+ trySendNextFrame();
}
+ return sys::MessageNone{};
+ }
+
+ bool ServiceGUI::isNextFrameReady() const noexcept
+ {
+ // Even if the next render is already cached, if any context in the pool is currently being processed, then we
+ // better wait for it.
+ return cachedRender.has_value() && !contextPool->isAnyContextLocked();
+ }
- if (commands.empty() == false) {
- sendToRender();
+ void ServiceGUI::trySendNextFrame()
+ {
+ const auto contextId = cachedRender->contextId;
+ if (const auto context = contextPool->borrowContext(contextId); context != nullptr) {
+ sendOnDisplay(context, contextId, cachedRender->refreshMode);
}
- return nullptr;
+ invalidateCache();
+ }
+
+ void ServiceGUI::setState(State state) noexcept
+ {
+ currentState = state;
}
+ bool ServiceGUI::isInState(State state) const noexcept
+ {
+ return currentState == state;
+ }
} /* namespace sgui */
M module-services/service-gui/ServiceGUI.hpp => module-services/service-gui/ServiceGUI.hpp +51 -63
@@ 3,20 3,20 @@
#pragma once
-// module-gui
-
#include <Common.hpp>
-#include <gui/core/Context.hpp>
-#include <gui/core/Renderer.hpp>
-#include <gui/input/Translator.hpp>
#include <Service/Common.hpp>
#include <Service/Message.hpp>
#include <Service/Service.hpp>
+#include <Service/Timer.hpp>
+
+#include "messages/RenderingFinished.hpp"
+
+#include "ContextPool.hpp"
+#include "DrawCommandsQueue.hpp"
#include <cstdint>
#include <memory>
#include <string>
-#include <vector>
namespace gui
{
@@ 24,76 24,64 @@ namespace gui
class DrawCommand;
} // namespace gui
-namespace sgui
+namespace service::gui
{
-
class WorkerGUI;
class ServiceGUI : public sys::Service
{
friend WorkerGUI;
- protected:
- // this is where every incomming frame is painted.
- gui::Context *renderContext;
- // this buffer is provided to eink
- gui::Context *transferContext;
- // ID of the last rendered frame
- uint32_t renderFrameCounter;
- // ID of the last frame sent to eink for rendering
- uint32_t transferedFrameCounter;
- // Horizontal size of the screen in pixels
- uint32_t screenWidth;
- // vertical size of the screen in pixels
- uint32_t screenHeight;
- // object responsible for rendering images to context
- gui::Renderer renderer;
- // flag that defines whether eink is ready for new frame buffer
- volatile bool einkReady = false;
- volatile bool requestSent = false;
- volatile bool rendering = false;
- // set of commands recently received. If this vector is not empty and new set of commands is received
- // previous commands are removed.
- std::list<std::unique_ptr<gui::DrawCommand>> commands;
- // uint32_t timer_id= 0;
- gui::RefreshModes mode = gui::RefreshModes::GUI_REFRESH_DEEP;
-
- // semaphore used to protect commands vector while commands are taken from service to worker.
- SemaphoreHandle_t semCommands;
-
- std::unique_ptr<WorkerGUI> worker;
-
- /**
- * Flag controls process of redrawing screen when suspend is in progress.
- */
- bool suspendInProgress = false;
- /**
- * Flag controls process of redrawing screen when phone is shutting down.
- */
- bool shutdownInProgress = false;
-
- void sendBuffer();
- void sendToRender();
-
public:
- ServiceGUI(const std::string &name,
- std::string parent = "",
- uint32_t screenWidth = 480,
- uint32_t screenHeight = 600);
- ~ServiceGUI();
-
- sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) override;
+ explicit ServiceGUI(const std::string &name, std::string parent = {});
+ ~ServiceGUI() noexcept override;
sys::ReturnCodes InitHandler() override;
-
sys::ReturnCodes DeinitHandler() override;
-
- sys::ReturnCodes SwitchPowerModeHandler(const sys::ServicePowerMode mode) override final;
+ sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) override;
+ sys::ReturnCodes SwitchPowerModeHandler(const sys::ServicePowerMode mode) override;
private:
+ struct CachedRender
+ {
+ int contextId;
+ ::gui::RefreshModes refreshMode;
+ };
+ enum class State
+ {
+ NotInitialised,
+ Idle,
+ Busy,
+ Suspended
+ };
+
+ static void initAssetManagers();
+ void registerMessageHandlers();
+
+ void cacheRender(int contextId, ::gui::RefreshModes refreshMode);
+ void invalidateCache();
+
+ void prepareDisplay(::gui::RefreshModes refreshMode);
+ void notifyRenderer(std::list<std::unique_ptr<::gui::DrawCommand>> &&commands, ::gui::RefreshModes refreshMode);
+ void enqueueDrawCommands(DrawCommandsQueue::QueueItem &&item);
+ void sendOnDisplay(::gui::Context *context, int contextId, ::gui::RefreshModes refreshMode);
+ void scheduleContextRelease(int contextId);
+ bool isNextFrameReady() const noexcept;
+ void trySendNextFrame();
+
+ void setState(State state) noexcept;
+ bool isInState(State state) const noexcept;
+
sys::MessagePointer handleDrawMessage(sys::Message *message);
sys::MessagePointer handleGUIRenderingFinished(sys::Message *message);
- sys::MessagePointer handleGUIDisplayReady(sys::Message *message);
- };
+ sys::MessagePointer handleEinkReady(sys::Message *message);
+ sys::MessagePointer handleImageDisplayedNotification(sys::Message *message);
-} /* namespace sgui */
+ std::unique_ptr<ContextPool> contextPool;
+ std::unique_ptr<WorkerGUI> worker;
+ std::unique_ptr<DrawCommandsQueue> commandsQueue;
+ std::optional<CachedRender> cachedRender;
+ std::unique_ptr<sys::Timer> contextReleaseTimer;
+ State currentState;
+ };
+} // namespace service::gui
A module-services/service-gui/SynchronizationMechanism.cpp => module-services/service-gui/SynchronizationMechanism.cpp +58 -0
@@ 0,0 1,58 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include "SynchronizationMechanism.hpp"
+
+namespace service::gui
+{
+ class FreeRtosSynchronization : public SynchronizationMechanism
+ {
+ public:
+ FreeRtosSynchronization();
+ FreeRtosSynchronization(const FreeRtosSynchronization &) = delete;
+ FreeRtosSynchronization(FreeRtosSynchronization &&) noexcept = default;
+ FreeRtosSynchronization &operator=(const FreeRtosSynchronization &) = delete;
+ FreeRtosSynchronization &operator=(FreeRtosSynchronization &&) noexcept = default;
+ ~FreeRtosSynchronization() noexcept override;
+
+ void wait(cpp_freertos::Mutex &lock, std::chrono::milliseconds timeout, Predicate predicate) override;
+ void notify() override;
+
+ private:
+ SemaphoreHandle_t semaphore;
+ };
+
+ FreeRtosSynchronization::FreeRtosSynchronization() : semaphore{xSemaphoreCreateBinary()}
+ {}
+
+ FreeRtosSynchronization::~FreeRtosSynchronization() noexcept
+ {
+ vSemaphoreDelete(semaphore);
+ }
+
+ void FreeRtosSynchronization::wait(cpp_freertos::Mutex &lock,
+ std::chrono::milliseconds timeout,
+ Predicate predicate)
+ {
+ if (!predicate) {
+ // Predicate needed to cover spurious wake-ups.
+ throw std::invalid_argument{"Null predicate passed."};
+ }
+
+ while (!predicate()) {
+ lock.Unlock();
+ xSemaphoreTake(semaphore, timeout.count());
+ lock.Lock();
+ }
+ }
+
+ void FreeRtosSynchronization::notify()
+ {
+ xSemaphoreGive(semaphore);
+ }
+
+ std::unique_ptr<SynchronizationMechanism> getFreeRtosSynchronizationMechanism()
+ {
+ return std::make_unique<FreeRtosSynchronization>();
+ }
+} // namespace service::gui
A module-services/service-gui/SynchronizationMechanism.hpp => module-services/service-gui/SynchronizationMechanism.hpp +26 -0
@@ 0,0 1,26 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#pragma once
+
+#include <mutex.hpp>
+
+#include <chrono>
+#include <functional>
+#include <memory>
+
+namespace service::gui
+{
+ /// Interface providing platform-independent synchronization strategy.
+ class SynchronizationMechanism
+ {
+ public:
+ using Predicate = std::function<bool()>;
+
+ virtual ~SynchronizationMechanism() noexcept = default;
+ virtual void wait(cpp_freertos::Mutex &lock, std::chrono::milliseconds timeout, Predicate predicate) = 0;
+ virtual void notify() = 0;
+ };
+
+ std::unique_ptr<SynchronizationMechanism> getFreeRtosSynchronizationMechanism();
+} // namespace service::gui
M module-services/service-gui/WorkerGUI.cpp => module-services/service-gui/WorkerGUI.cpp +34 -31
@@ 5,52 5,55 @@
#include <DrawCommand.hpp>
#include <log/log.hpp>
-#include <MessageType.hpp>
-#include <projdefs.h>
-#include <queue.h>
#include <Renderer.hpp>
-#include <semphr.h>
#include <Service/Bus.hpp>
-#include <Service/Message.hpp>
-#include <Service/Service.hpp>
#include <Service/Worker.hpp>
#include <service-gui/ServiceGUI.hpp>
#include <memory>
-#include <utility>
-#include <vector>
+#include <module-utils/time/ScopedTime.hpp>
#include "messages/RenderingFinished.hpp"
-namespace sgui
+namespace service::gui
{
-
- WorkerGUI::WorkerGUI(ServiceGUI *service) : Worker(service), service(service)
+ WorkerGUI::WorkerGUI(ServiceGUI *service) : Worker(service), guiService{service}
{}
- bool WorkerGUI::handleMessage(uint32_t queueID)
+ void WorkerGUI::notify(Signal command)
{
- auto &queue = queues[queueID];
-
- auto serviceGUI = static_cast<sgui::ServiceGUI *>(service);
-
- if (queueID == 0) {
- sys::WorkerCommand received;
- queue->Dequeue(&received, 0);
+ if (auto queue = getQueueByName(SignallingQueueName); !queue->Overwrite(&command)) {
+ LOG_ERROR("Unable to overwrite the command in the commands queue.");
+ }
+ }
- std::list<std::unique_ptr<gui::DrawCommand>> uniqueCommands;
- if (xSemaphoreTake(serviceGUI->semCommands, pdMS_TO_TICKS(1000)) == pdTRUE) {
- uniqueCommands = std::move(serviceGUI->commands);
- xSemaphoreGive(serviceGUI->semCommands);
- }
- else {
- LOG_ERROR("Failed to acquire semaphore");
+ bool WorkerGUI::handleMessage(uint32_t queueID)
+ {
+ if (const auto queue = queues[queueID]; queue->GetQueueName() == SignallingQueueName) {
+ if (sys::WorkerCommand command; queue->Dequeue(&command, 0)) {
+ handleCommand(static_cast<Signal>(command.command));
}
-
- serviceGUI->renderer.render(serviceGUI->renderContext, uniqueCommands);
-
- sys::Bus::SendUnicast(std::make_shared<service::gui::RenderingFinished>(), service->GetName(), service);
}
return true;
}
-} /* namespace sgui */
+ void WorkerGUI::handleCommand(Signal command)
+ {
+ if (command == Signal::Render) {
+ auto item = guiService->commandsQueue->dequeue();
+ render(item.commands, item.refreshMode);
+ }
+ }
+
+ void WorkerGUI::render(std::list<std::unique_ptr<::gui::DrawCommand>> &commands, ::gui::RefreshModes refreshMode)
+ {
+ const auto [contextId, context] = guiService->contextPool->borrowContext(); // Waits for the context.
+ renderer.render(context, commands);
+ onRenderingFinished(contextId, refreshMode);
+ }
+
+ void WorkerGUI::onRenderingFinished(int contextId, ::gui::RefreshModes refreshMode)
+ {
+ auto msg = std::make_shared<service::gui::RenderingFinished>(contextId, refreshMode);
+ sys::Bus::SendUnicast(std::move(msg), guiService->GetName(), guiService);
+ }
+} // namespace service::gui
M module-services/service-gui/WorkerGUI.hpp => module-services/service-gui/WorkerGUI.hpp +22 -24
@@ 12,34 12,32 @@
#include <cstdint>
-namespace sgui
+namespace service::gui
{
-
class ServiceGUI;
- enum class WorkerGUICommands
- {
- Finish,
- Render,
- // RenderSuspend
- };
-
- /*
- *
- */
class WorkerGUI : public sys::Worker
{
- // object responsible for rendering images to context
- gui::Renderer renderer;
- sys::Service *service;
-
public:
- WorkerGUI(ServiceGUI *service);
-
- /**
- * virtual method responsible for finishing the worker and handling rendering commands
- */
- bool handleMessage(uint32_t queueID) override;
+ enum class Signal
+ {
+ Render
+ };
+ static constexpr auto SignallingQueueName = "SignallingQueue";
+ static constexpr auto SignallingQueueCapacity = 1;
+ static constexpr auto SignalSize = sizeof(Signal);
+
+ explicit WorkerGUI(ServiceGUI *service);
+
+ void notify(Signal command);
+ auto handleMessage(std::uint32_t queueID) -> bool override;
+
+ private:
+ void handleCommand(Signal command);
+ void render(std::list<std::unique_ptr<::gui::DrawCommand>> &commands, ::gui::RefreshModes refreshMode);
+ void onRenderingFinished(int contextId, ::gui::RefreshModes refreshMode);
+
+ ServiceGUI *guiService;
+ ::gui::Renderer renderer;
};
-
-} /* namespace sgui */
+} // namespace service::gui
M module-services/service-gui/messages/DrawMessage.cpp => module-services/service-gui/messages/DrawMessage.cpp +4 -6
@@ 6,11 6,9 @@
#include <Common.hpp>
#include <DrawCommand.hpp>
-namespace sgui
+namespace service::gui
{
-
- DrawMessage::DrawMessage(std::list<gui::Command> commands, gui::RefreshModes mode)
+ DrawMessage::DrawMessage(std::list<::gui::Command> commands, ::gui::RefreshModes mode)
: GUIMessage(), mode(mode), commands(std::move(commands))
- {
- }
-} /* namespace sgui */
+ {}
+} // namespace service::gui
M module-services/service-gui/messages/DrawMessage.hpp => module-services/service-gui/messages/DrawMessage.hpp +9 -11
@@ 16,9 16,8 @@
#include "GUIMessage.hpp"
#include "gui/Common.hpp"
-namespace sgui
+namespace service::gui
{
-
class DrawMessage : public GUIMessage
{
public:
@@ 30,20 29,19 @@ namespace sgui
} type = Type::NORMAL;
public:
- gui::RefreshModes mode;
- std::list<gui::Command> commands;
+ ::gui::RefreshModes mode;
+ std::list<::gui::Command> commands;
- DrawMessage(std::list<gui::Command> commandsList, gui::RefreshModes mode);
+ DrawMessage(std::list<::gui::Command> commandsList, ::gui::RefreshModes mode);
- void setCommandType(Type type) noexcept
+ void setCommandType(Type value) noexcept
{
- this->type = type;
+ type = value;
}
- bool isType(Type type) const noexcept
+ bool isType(Type value) const noexcept
{
- return this->type == type;
+ return type == value;
}
};
-
-} // namespace sgui
+} // namespace service::gui
R module-services/service-gui/messages/GUIDisplayReady.hpp => module-services/service-gui/messages/EinkReady.hpp +9 -14
@@ 1,31 1,26 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
#include "GUIMessage.hpp"
+#include <gui/Common.hpp>
+
namespace service::gui
{
- class GUIDisplayReady : public sgui::GUIMessage
+ class EinkReady : public GUIMessage
{
-
- bool suspendInProgress = false;
- bool shutdownInProgress = false;
-
public:
- GUIDisplayReady(bool suspendInProgress, bool shutdownInProgress)
- : suspendInProgress(suspendInProgress), shutdownInProgress(shutdownInProgress)
+ explicit EinkReady(::gui::Size displaySize) : einkDisplaySize{displaySize}
{}
- [[nodiscard]] auto getSuspendInProgress() const noexcept
+ [[nodiscard]] auto getDisplaySize() const noexcept -> ::gui::Size
{
- return suspendInProgress;
+ return einkDisplaySize;
}
- [[nodiscard]] auto getShutdownInProgress() const noexcept
- {
- return shutdownInProgress;
- }
+ private:
+ ::gui::Size einkDisplaySize;
};
} // namespace service::gui
M module-services/service-gui/messages/GUIMessage.hpp => module-services/service-gui/messages/GUIMessage.hpp +4 -5
@@ 7,13 7,12 @@
#include <Service/Message.hpp>
#include <core/DrawCommand.hpp>
-namespace sgui
+namespace service::gui
{
-
class GUIMessage : public sys::DataMessage
{
public:
- GUIMessage() : sys::DataMessage(MessageType::GUIMessage){};
+ GUIMessage() : sys::DataMessage(MessageType::GUIMessage)
+ {}
};
-
-} /* namespace sgui */
+} // namespace service::gui
M module-services/service-gui/messages/RenderingFinished.hpp => module-services/service-gui/messages/RenderingFinished.hpp +23 -2
@@ 5,8 5,29 @@
#include "GUIMessage.hpp"
+#include <gui/Common.hpp>
+
namespace service::gui
{
- class RenderingFinished : public sgui::GUIMessage
- {};
+ class RenderingFinished : public GUIMessage
+ {
+ public:
+ RenderingFinished(int contextId, ::gui::RefreshModes refreshMode)
+ : contextId{contextId}, refreshMode{refreshMode}
+ {}
+
+ [[nodiscard]] int getContextId() const noexcept
+ {
+ return contextId;
+ }
+
+ [[nodiscard]] ::gui::RefreshModes getRefreshMode() const noexcept
+ {
+ return refreshMode;
+ }
+
+ private:
+ int contextId;
+ ::gui::RefreshModes refreshMode;
+ };
} // namespace service::gui
A module-services/service-gui/tests/CMakeLists.txt => module-services/service-gui/tests/CMakeLists.txt +21 -0
@@ 0,0 1,21 @@
+add_catch2_executable(
+ NAME
+ context-pool-tests
+ SRCS
+ tests-main.cpp
+ test-ContextPool.cpp
+ MockedSynchronizationMechanism.hpp
+ LIBS
+ service-gui
+)
+
+add_catch2_executable(
+ NAME
+ commands-queue-tests
+ SRCS
+ tests-main.cpp
+ test-DrawCommandsQueue.cpp
+ MockedSynchronizationMechanism.hpp
+ LIBS
+ service-gui
+)
A module-services/service-gui/tests/MockedSynchronizationMechanism.hpp => module-services/service-gui/tests/MockedSynchronizationMechanism.hpp +31 -0
@@ 0,0 1,31 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <SynchronizationMechanism.hpp>
+
+#include "mutex.hpp"
+
+#include <chrono>
+#include <thread>
+
+class MockedSynchronizationMechanism : public service::gui::SynchronizationMechanism
+{
+ public:
+ void wait(cpp_freertos::Mutex &lock, std::chrono::milliseconds timeout, Predicate predicate) override;
+ void notify() override;
+};
+
+void MockedSynchronizationMechanism::wait(cpp_freertos::Mutex &lock,
+ std::chrono::milliseconds timeout,
+ Predicate predicate)
+{
+ using namespace std::chrono_literals;
+ while (!predicate()) {
+ lock.Unlock();
+ std::this_thread::sleep_for(10ms);
+ lock.Lock();
+ }
+}
+
+void MockedSynchronizationMechanism::notify()
+{}
A module-services/service-gui/tests/test-ContextPool.cpp => module-services/service-gui/tests/test-ContextPool.cpp +53 -0
@@ 0,0 1,53 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <catch2/catch.hpp>
+
+#include "ContextPool.hpp"
+#include "MockedSynchronizationMechanism.hpp"
+
+#include <chrono>
+#include <thread>
+
+using namespace service::gui;
+
+TEST_CASE("ContextPoolTests")
+{
+ const ::gui::Size displaySize{1, 1};
+ constexpr auto ContextPoolCapacity = 1;
+ constexpr auto ContextPoolFirstId = 0;
+ ContextPool contextPool{displaySize, ContextPoolCapacity, std::make_unique<MockedSynchronizationMechanism>()};
+ REQUIRE(!contextPool.isAnyContextLocked());
+
+ SECTION("Borrow context when all are used")
+ {
+ const auto [contextId, context] = contextPool.borrowContext();
+ REQUIRE(contextId == ContextPoolFirstId);
+ REQUIRE(contextPool.isAnyContextLocked());
+
+ std::thread thr{[&contextPool, id = contextId]() {
+ std::this_thread::sleep_for(std::chrono::milliseconds{500});
+ contextPool.returnContext(id);
+ }};
+ const auto [nextContextId, nextContext] = contextPool.borrowContext();
+ REQUIRE(contextId == nextContextId);
+
+ if (thr.joinable()) {
+ thr.join();
+ }
+ contextPool.returnContext(nextContextId);
+ REQUIRE(!contextPool.isAnyContextLocked());
+ }
+
+ SECTION("Return unused context")
+ {
+ REQUIRE(!contextPool.isAnyContextLocked());
+ REQUIRE_NOTHROW(contextPool.returnContext(0));
+ }
+
+ SECTION("Return invalid context id")
+ {
+ REQUIRE(!contextPool.isAnyContextLocked());
+ REQUIRE_NOTHROW(contextPool.returnContext(10));
+ }
+}
A module-services/service-gui/tests/test-DrawCommandsQueue.cpp => module-services/service-gui/tests/test-DrawCommandsQueue.cpp +73 -0
@@ 0,0 1,73 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#include <catch2/catch.hpp>
+
+#include "DrawCommandsQueue.hpp"
+#include "MockedSynchronizationMechanism.hpp"
+
+#include <chrono>
+#include <thread>
+
+using namespace service::gui;
+
+TEST_CASE("DrawCommandsQueueTests")
+{
+ constexpr auto ExpectedQueueSize = 4U;
+ DrawCommandsQueue queue{ExpectedQueueSize, std::make_unique<MockedSynchronizationMechanism>()};
+
+ SECTION("Enqueue")
+ {
+ queue.enqueue(DrawCommandsQueue::QueueItem{});
+ REQUIRE(queue.size() == 1U);
+ }
+
+ SECTION("Single-thread dequeue")
+ {
+ queue.enqueue(DrawCommandsQueue::QueueItem{});
+ REQUIRE(queue.size() == 1U);
+
+ const auto item = queue.dequeue();
+ REQUIRE(item.commands.empty());
+ REQUIRE(item.refreshMode == ::gui::RefreshModes::GUI_REFRESH_FAST);
+ REQUIRE(queue.size() == 0);
+ }
+
+ SECTION("Multi-thread dequeue")
+ {
+ std::thread thr{[&queue]() {
+ std::this_thread::sleep_for(std::chrono::milliseconds{100});
+ queue.enqueue(DrawCommandsQueue::QueueItem{});
+ }};
+
+ const auto item = queue.dequeue();
+ if (thr.joinable()) {
+ thr.join();
+ }
+
+ REQUIRE(item.commands.empty());
+ REQUIRE(item.refreshMode == ::gui::RefreshModes::GUI_REFRESH_FAST);
+ REQUIRE(queue.size() == 0U);
+ }
+
+ SECTION("Clear")
+ {
+ queue.enqueue(DrawCommandsQueue::QueueItem{});
+ queue.enqueue(DrawCommandsQueue::QueueItem{});
+ REQUIRE(queue.size() == 2U);
+
+ queue.clear();
+ REQUIRE(queue.size() == 0);
+ }
+
+ SECTION("Clear and get max refresh mode")
+ {
+ queue.enqueue(DrawCommandsQueue::QueueItem{});
+ queue.enqueue(DrawCommandsQueue::QueueItem{{}, ::gui::RefreshModes::GUI_REFRESH_DEEP});
+ REQUIRE(queue.size() == 2U);
+
+ const auto maxRefreshMode = queue.getMaxRefreshModeAndClear();
+ REQUIRE(queue.size() == 0);
+ REQUIRE(maxRefreshMode == ::gui::RefreshModes::GUI_REFRESH_DEEP);
+ }
+}
A module-services/service-gui/tests/tests-main.cpp => module-services/service-gui/tests/tests-main.cpp +5 -0
@@ 0,0 1,5 @@
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
+// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
+
+#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
+#include <catch2/catch.hpp>
M module-sys/Service/Bus.cpp => module-sys/Service/Bus.cpp +2 -1
@@ 26,7 26,7 @@ namespace sys
{
CriticalSection::Enter();
for (auto &w : service->busChannels) {
- channels[w].m_services.push_back(service);
+ channels[w].m_services.insert(service);
}
servicesRegistered[service->GetName()] = service;
CriticalSection::Exit();
@@ 195,6 195,7 @@ namespace sys
msg->transType = Message::TransmissionType ::Multicast;
msg->sender = source->GetName();
+
for (auto const &w : channels[channel].m_services) {
w->mailbox.push(msg);
}
M module-sys/Service/Channel.hpp => module-sys/Service/Channel.hpp +2 -1
@@ 4,6 4,7 @@
#pragma once
#include <memory>
+#include <set>
namespace sys
{
@@ 17,7 18,7 @@ namespace sys
Channel()
{}
- std::vector<std::shared_ptr<Service>> m_services;
+ std::set<std::shared_ptr<Service>> m_services;
};
} // namespace sys
M module-sys/Service/Timer.cpp => module-sys/Service/Timer.cpp +6 -3
@@ 1,4 1,4 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#include "Timer.hpp"
@@ 67,6 67,7 @@ namespace sys
void Timer::start()
{
log_timers("Timer %s start!", name.c_str());
+ isActive = true;
Start(0);
}
@@ 78,13 79,15 @@ namespace sys
SetPeriod(pdMS_TO_TICKS(interval));
}
log_timers("Timer %s reload!", name.c_str());
+ isActive = true;
Start(0); // start with no waittime
}
void Timer::stop()
{
log_timers("Timer %s stop!", name.c_str());
- callback = nullptr;
+ // make sure callback is not called even if it is already in the queue
+ isActive = false;
Stop(0);
}
@@ 98,7 101,7 @@ namespace sys
void Timer::onTimeout()
{
log_timers("Timer %s tick", name.c_str());
- if (callback != nullptr) {
+ if (callback != nullptr && isActive) {
log_timers("Timer %s callback run!", name.c_str());
callback(*this);
}
M module-sys/Service/Timer.hpp => module-sys/Service/Timer.hpp +6 -4
@@ 1,13 1,14 @@
-// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
+// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
#include "FreeRTOS.h"
-#include "portmacro.h" // for TickType_t
+#include "portmacro.h" // for TickType_t
#include <module-os/RTOSWrapper/include/timer.hpp> // for Timer
-#include <functional> // for function
-#include <string> // for string
+#include <functional> // for function
+#include <string> // for string
+#include <atomic>
namespace sys
{
class Service;
@@ 69,6 70,7 @@ namespace sys
Type type;
ms interval;
std::function<void(Timer &)> callback = nullptr;
+ std::atomic_bool isActive = false;
const std::string name;
public:
M module-sys/SystemManager/PowerManager.cpp => module-sys/SystemManager/PowerManager.cpp +18 -1
@@ 53,7 53,16 @@ namespace sys
void PowerManager::IncreaseCpuFrequency() const
{
- bsp::LowPowerMode::CpuFrequency freq = lowPowerControl->GetCurrentFrequency();
+ const auto freq = lowPowerControl->GetCurrentFrequency();
+ const auto oscSource = lowPowerControl->GetCurrentOscillatorSource();
+
+ /// switch osc source first
+ if (freq == bsp::LowPowerMode::CpuFrequency::Level_1 &&
+ oscSource == bsp::LowPowerMode::OscillatorSource::Internal) {
+ lowPowerControl->SwitchOscillatorSource(bsp::LowPowerMode::OscillatorSource::External);
+ }
+
+ /// then increase frequency
if (freq < bsp::LowPowerMode::CpuFrequency::Level_6) {
lowPowerControl->SetCpuFrequency(bsp::LowPowerMode::CpuFrequency::Level_6);
}
@@ 62,6 71,7 @@ namespace sys
void PowerManager::DecreaseCpuFrequency() const
{
const auto freq = lowPowerControl->GetCurrentFrequency();
+ const auto oscSource = lowPowerControl->GetCurrentOscillatorSource();
auto level = bsp::LowPowerMode::CpuFrequency::Level_1;
switch (freq) {
@@ 84,9 94,16 @@ namespace sys
break;
}
+ /// decrease frequency first
if (level != freq) {
lowPowerControl->SetCpuFrequency(level);
}
+
+ /// then switch osc source
+ if (level == bsp::LowPowerMode::CpuFrequency::Level_1 &&
+ oscSource == bsp::LowPowerMode::OscillatorSource::External) {
+ lowPowerControl->SwitchOscillatorSource(bsp::LowPowerMode::OscillatorSource::Internal);
+ }
}
void PowerManager::ResetFrequencyShiftCounter()
M module-sys/SystemManager/SystemManager.cpp => module-sys/SystemManager/SystemManager.cpp +6 -1
@@ 256,7 256,12 @@ namespace sys
});
connect(sevm::BatteryLevelCriticalMessage(), [&](Message *) {
- LOG_INFO("Battery Critical Level reached!");
+ LOG_INFO("Battery Critical SOC Level reached!");
+ return MessageNone{};
+ });
+
+ connect(sevm::BatteryBrownoutMessage(), [&](Message *) {
+ LOG_INFO("Battery Brownout voltage level reached!");
return MessageNone{};
});
M source/MessageType.hpp => source/MessageType.hpp +3 -0
@@ 80,6 80,8 @@ enum class MessageType
CellularSimResponse, // Send to PIN window (show, error state, hide)
CellularSimVerifyPinRequest, // Send from PIN window with PIN, PUK, ... number
+ CellularPacketData, ///< for all PacketData messages
+
CellularGetOwnNumber,
CellularGetIMSI,
CellularGetNetworkInfo,
@@ 168,6 170,7 @@ enum class MessageType
EVMChargerPlugged,
EVMBatterySetCriticalLevel,
EVMBatteryLevelCritical,
+ EVMBatteryBrownout,
// rtc messages
EVMMinuteUpdated, ///< This message is send to current focused application on every minute time change.
EVMTimeUpdated, ///< This message is send on every time update.