Merge branch 'release-candidate' into release

This commit is contained in:
Aleksandr Kutuzov 2022-01-21 17:12:55 +03:00
commit 3e04b73c1e
No known key found for this signature in database
GPG Key ID: 0D0011717914BBCD
1296 changed files with 388979 additions and 378211 deletions

View File

@ -1,6 +1,6 @@
name: 'Build'
on:
on:
push:
branches:
- dev
@ -14,8 +14,8 @@ env:
DEFAULT_TARGET: f7
jobs:
build:
runs-on: [self-hosted]
main:
runs-on: [self-hosted,Office]
steps:
- name: 'Cleanup workspace'
uses: AutoModality/action-clean@v1
@ -35,13 +35,6 @@ jobs:
submodules: true
ref: ${{ github.event.pull_request.head.sha }}
- name: 'Docker cache'
uses: satackey/action-docker-layer-caching@v0.0.11
continue-on-error: true
with:
key: docker-cache-${{ hashFiles('docker/**') }}-{hash}
restore-keys: docker-cache-${{ hashFiles('docker/**') }}-
- name: 'Build docker image'
uses: ./.github/actions/docker
@ -78,6 +71,14 @@ jobs:
run: |
tar czpf artifacts/flipper-z-any-scripts-${{steps.names.outputs.suffix}}.tgz scripts
- name: 'Rebuild Assets'
uses: ./.github/actions/docker
with:
run: |
set -e
make -C assets clean
make -C assets
- name: 'Build the firmware in docker'
uses: ./.github/actions/docker
with:
@ -85,7 +86,7 @@ jobs:
set -e
for TARGET in ${TARGETS}
do
make TARGET=${TARGET}
make TARGET=${TARGET} ${{ startsWith(github.ref, 'refs/tags') && 'DEBUG=0 COMPACT=1' || '' }}
done
- name: 'Move upload files'
@ -149,3 +150,65 @@ jobs:
body: |
[Click here](https://update.flipperzero.one/?url=https://update.flipperzero.one/builds/firmware/${{steps.names.outputs.artifacts-path}}/flipper-z-${{steps.names.outputs.default-target}}-full-${{steps.names.outputs.suffix}}.dfu&channel=${{steps.names.outputs.artifacts-path}}&version=${{steps.names.outputs.short-hash}}&target=${{steps.names.outputs.default-target}}) to flash the `${{steps.names.outputs.short-hash}}` version of this branch via WebUSB.
edit-mode: replace
compact:
if: ${{ !startsWith(github.ref, 'refs/tags') }}
runs-on: [self-hosted,koteeq]
steps:
- name: 'Cleanup workspace'
uses: AutoModality/action-clean@v1
- name: 'Decontaminate previous build leftovers'
run: |
if [ -d .git ]
then
git submodule status \
|| git checkout `git rev-list --max-parents=0 HEAD | tail -n 1`
fi
- name: 'Checkout code'
uses: actions/checkout@v2
with:
fetch-depth: 0
submodules: true
ref: ${{ github.event.pull_request.head.sha }}
- name: 'Build docker image'
uses: ./.github/actions/docker
- name: 'Generate suffix and folder name'
id: names
run: |
REF=${{ github.ref }}
if [[ ${{ github.event_name }} == 'pull_request' ]]; then
REF=${{ github.head_ref }}
fi
BRANCH_OR_TAG=${REF#refs/*/}
SHA=$(git rev-parse --short HEAD)
if [[ "${{ github.ref }}" == "refs/tags/"* ]]; then
SUFFIX=${BRANCH_OR_TAG//\//_}
else
SUFFIX=${BRANCH_OR_TAG//\//_}-$(date +'%d%m%Y')-${SHA}
fi
echo "WORKFLOW_BRANCH_OR_TAG=${BRANCH_OR_TAG}" >> $GITHUB_ENV
echo "DIST_SUFFIX=${SUFFIX}" >> $GITHUB_ENV
- name: 'Rebuild Assets'
uses: ./.github/actions/docker
with:
run: |
set -e
make -C assets clean
make -C assets
- name: 'Build the firmware in docker'
uses: ./.github/actions/docker
with:
run: |
set -e
for TARGET in ${TARGETS}
do
make TARGET=${TARGET} DEBUG=0 COMPACT=1
done

17
.github/workflows/check_submodules.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: 'Check submodules'
on:
pull_request:
jobs:
protobuf:
runs-on: ubuntu-latest
steps:
- name: 'Checkout code'
uses: actions/checkout@v2
- name: 'Check submodule commit branch'
uses: jtmullen/submodule-branch-check-action@v1
with:
path: assets/protobuf
branch: dev
fetch_depth: 50

View File

@ -14,7 +14,7 @@ env:
jobs:
lint_c_cpp:
runs-on: [self-hosted]
runs-on: [self-hosted,Office]
steps:
- name: 'Cleanup workspace'
uses: AutoModality/action-clean@v1
@ -47,7 +47,7 @@ jobs:
id: syntax_check
uses: ./.github/actions/docker
with:
run: SET_GH_OUTPUT=1 /syntax_check.sh
run: SET_GH_OUTPUT=1 make lint
- name: Report code formatting errors
if: failure() && steps.syntax_check.outputs.errors && github.event.pull_request
@ -59,4 +59,4 @@ jobs:
```
${{ steps.syntax_check.outputs.errors }}
```
You might want to run `docker compose exec dev /syntax_check.sh` for an auto-fix.
You might want to run `docker compose exec dev make format` for an auto-fix.

View File

@ -7,7 +7,7 @@ on:
jobs:
reindex:
name: 'Reindex updates'
runs-on: [self-hosted]
runs-on: [self-hosted,Office]
steps:
- name: Trigger reindex
uses: wei/curl@master

101
CODING_STYLE.md Normal file
View File

@ -0,0 +1,101 @@
# Intro
Nice to see you reading this document, we really appreciate it.
As all documents of this kind it's unable to cover everything.
But it will cover general rules that we enforcing on PR review.
Also we already have automatic rules checking and formatting,
but it got it's limitations and this guide is still mandatory.
Some part of this project do have it's own naming and coding guides.
For example: assets. Take a look into `ReadMe.md` in assets folder for more details.
Also 3rd party libraries are none of our concern.
And yes, this set is not final and we are open to discussion.
If you want to add/remove/change something here please feel free to open new ticket.
# Inspiration
Our guide is inspired by, but not claiming to be compatible with:
- https://www.kernel.org/doc/html/v4.10/process/coding-style.html
- https://docs.unrealengine.com/en-US/Programming/Development/CodingStandard
- https://webkit.org/code-style-guidelines/
# General rules
## Readability and Simplicity first
Code we write is intended to be public.
Avoid one-liners from hell and keep code complexity under control.
Try to make code self explanatory and add comments if needed.
Leave references to standards that you are implementing.
Use project wiki to document new/reverse engineered standards.
## Variable and function names must clearly define what it's doing
It's ok if it will be long, but it should clearly state what it's doing, without need to dive into code.
This also applies to function/method's code.
Try to avoid one letter variables.
## Encapsulation
Don't expose raw data, provide methods to work with it.
Almost everything in flipper firmware is built around this concept.
# C coding style
- Tab is 4 spaces
- Use `make format` to reformat source code and check style guide before commit
## Naming
### Type names are CamelCase
Examples:
FuriHalUsb
Gui
SubghzKeystore
### Functions are snake_case
furi_hal_usb_init
gui_add_view_port
subghz_keystore_read
### File and Package name is a prefix for it's content
This rule makes easier to locate types, functions and sources.
For example:
We have abstraction that we call `Subghz Keystore`, so there will be:
file `subghz_keystore.h` we have type `SubghzKeystore` and function `subghz_keystore_read`.
### File names
- Directories: `^[0-9A-Za-z_]+$`
- File names: `^[0-9A-Za-z_]+\.[a-z]+$`
- File extensions: `[ ".h", ".c", ".cpp", ".cxx", ".hpp" ]`
Enforced by linter.
### Standard function/method names
Suffixes:
- `alloc` - allocate and init instance. C style constructor. Returns pointer to instance.
- `free` - deinit and release instance. C style destructor. Takes pointer to instance.
# C++ coding style
Work In Progress. Use C style guide as a base.
# Python coding style
- Tab is 4 spaces
- Use [black](https://pypi.org/project/black/) to reformat source code before commit

View File

@ -21,6 +21,7 @@ Before writing code and creating PR make sure that it aligns with our mission an
- All our devices are intended for research and education.
- PR that contains code intended to commit crimes is not going to be accepted.
- Your PR must comply with our [Coding Style](CODING_STYLE.md)
- Your PR must contain code compatiable with project [LICENSE](LICENSE).
- PR will only be merged if it pass CI/CD.
- PR will only be merged if it pass review by code owner.

View File

@ -1,7 +1,28 @@
PROJECT_ROOT := $(abspath $(dir $(abspath $(firstword $(MAKEFILE_LIST)))))
COPRO_DIR := $(PROJECT_ROOT)/lib/STM32CubeWB/Projects/STM32WB_Copro_Wireless_Binaries/STM32WB5x
NPROCS := 1
PROJECT_SOURCE_DIRECTORIES := \
$(PROJECT_ROOT)/applications \
$(PROJECT_ROOT)/bootloader/src \
$(PROJECT_ROOT)/bootloader/targets \
$(PROJECT_ROOT)/core \
$(PROJECT_ROOT)/firmware/targets \
$(PROJECT_ROOT)/lib/app-template \
$(PROJECT_ROOT)/lib/app-scened-template \
$(PROJECT_ROOT)/lib/common-api \
$(PROJECT_ROOT)/lib/cyfral \
$(PROJECT_ROOT)/lib/drivers \
$(PROJECT_ROOT)/lib/flipper_file \
$(PROJECT_ROOT)/lib/irda \
$(PROJECT_ROOT)/lib/nfc_protocols \
$(PROJECT_ROOT)/lib/ST25RFAL002 \
$(PROJECT_ROOT)/lib/onewire \
$(PROJECT_ROOT)/lib/qrcode \
$(PROJECT_ROOT)/lib/subghz \
$(PROJECT_ROOT)/lib/toolbox \
$(PROJECT_ROOT)/lib/u8g2
NPROCS := 3
OS := $(shell uname -s)
ifeq ($(OS), Linux)
@ -91,10 +112,12 @@ flash_radio_fus_please_i_m_not_going_to_complain:
@$(PROJECT_ROOT)/scripts/flash.py core2fus 0x080EC000 --statement=AGREE_TO_LOOSE_FLIPPER_FEATURES_THAT_USES_CRYPTO_ENCLAVE $(COPRO_DIR)/stm32wb5x_FUS_fw.bin
@$(PROJECT_ROOT)/scripts/ob.py set
FORMAT_SOURCES = $(shell find applications bootloader core -iname "*.h" -o -iname "*.c" -o -iname "*.cpp")
.PHONY: lint
lint:
@echo "Checking source code formatting"
@$(PROJECT_ROOT)/scripts/lint.py check $(PROJECT_SOURCE_DIRECTORIES)
.PHONY: format
format:
@echo "Formatting sources with clang-format"
@clang-format -style=file -i $(FORMAT_SOURCES)
@echo "Reformating sources code"
@$(PROJECT_ROOT)/scripts/lint.py format $(PROJECT_SOURCE_DIRECTORIES)

View File

@ -11,7 +11,6 @@ Our goal is to create nice and clean code with good documentation, to make it a
[Get Latest Firmware from Update Server](https://update.flipperzero.one/)
Flipper Zero's firmware consists of three components:
- Core2 firmware set - proprietary components by ST: FUS + radio stack. FUS is flashed at factory and you should never update it.
@ -135,72 +134,24 @@ make whole
```
# Links
* Discord: [flipp.dev/discord](https://flipp.dev/discord)
* Website: [flipperzero.one](https://flipperzero.one)
* Kickstarter page: [kickstarter.com](https://www.kickstarter.com/projects/flipper-devices/flipper-zero-tamagochi-for-hackers)
* Forum: [forum.flipperzero.one](https://forum.flipperzero.one/)
# Folders structure
# Project structure
- applications - application and services
* accessor - Wiegand server
* archive - Archive and file manager
* bt - BLE service and application
* cli - Console service
* debug_tools - different tools that we use on factory and for debug
* dialogs - service for showing GUI dialogs
* dolphin - dolphin service and supplementary apps
* gpio-tester - GPIO control application
* gui - GUI service
* ibutton - ibutton application, onewire keys and more
* input - input service
* irda - irda application, controls your IR devices
* irda_monitor - irda debug tool
* lfrfid - LF RFID application
* lfrfid-debug - LF RFID debug tool
* loader - application loader service
* menu - main menu service
* music-player - music player app (demo)
* nfc - NFC application, HF rfid, EMV and etc
* notification - notification service
* power - power service
* power-observer - power debug tool
* scened-app-example - c++ application example
* storage - storage service, internal + sdcard
* storage_settings - storage settings app
* subghz - subghz application, 433 fobs and etc
* tests - unit tests and etc
- assets - assets used by applications and services
* compiled - compilation results
* icons - source icons images
- bootloader - bootloader for flipper
* src - bootloader sources
* targets - targets' hal and implementation
- core - core libraries: home for furi
- debug - debug helpers, plugins and tools
- docker - docker image sources (used for automated firmware build)
- documentation - documentation generation system configs and input files
- firmware - firmware for flipper
* targets - targets' hal and implementation
- lib - different libraries and drivers that apps and firmware uses
* ST25RFAL002 - ST253916 driver and NFC hal
* STM32CubeWB - STM32WB hal
* app-scened-template - scened template app library
* app-template - template app library
* callback-connector - callback connector library
* common-api - common api declaration library
* cyfral - cyfral library
* drivers - drivers that we wrote
* fatfs - external storage file system
* fnv1a-hash - fnv1a hash library
* irda - irda library
* littlefs - internal storage file system
* mlib - algorithms and containers
* nfc_protocols - nfc protocols library
* onewire - one wire library
* qrcode - qr code generator library
* subghz - subghz library
* toolbox - toolbox of things that we are using but don't place in core
* u8g2 - graphics library that we use to draw GUI
- make - make helpers
- scripts - supplementary scripts
- `applications` - Applications and services used in firmware
- `assets` - Assets used by applications and services
- `bootloader` - Bootloader source code
- `core` - Furi Core: os level primitives and helpers
- `debug` - Debug tool: GDB-plugins, SVD-file and etc
- `docker` - Docker image sources (used for firmware build automation)
- `documentation` - Documentation generation system configs and input files
- `firmware` - Firmware source code
- `lib` - Our and 3rd party libraries, drivers and etc...
- `make` - Make helpers
- `scripts` - Supplementary scripts and python libraries home
Also pay attention to `ReadMe.md` files inside of those directories.

40
applications/ReadMe.md Normal file
View File

@ -0,0 +1,40 @@
# Structure
- `about` - Small About application that shows flipper info
- `accessor` - Wiegand server
- `archive` - Archive and file manager
- `bad_usb` - Bad USB application
- `bt` - BLE service and application
- `cli` - Console service and API
- `crypto` - Crypto cli tools
- `debug_tools` - Different tools that we use for debug
- `desktop` - Desktop service
- `dialogs` - Dialogs service: GUI Dialogs for your app
- `dolphin` - Dolphin service and supplementary apps
- `gpio` - GPIO application: includes USART bridge and GPIO control
- `gui` - GUI service and API
- `ibutton` - iButton application, onewire keys and more
- `input` - Input service
- `irda` - Irda application, controls your IR devices
- `irda_monitor` - Irda debug tool
- `lfrfid` - LF RFID application
- `lfrfid_debug` - LF RFID debug tool
- `loader` - Application loader service
- `music_player` - Music player app (demo)
- `nfc` - NFC application, HF rfid, EMV and etc
- `notification` - Notification service
- `power` - Power service
- `power_observer` - Power debug tool
- `rpc` - RPC service and API
- `scened_app_example` - C++ application example
- `snake_game` - Snake game application
- `storage` - Storage service, internal + sdcard
- `storage_settings` - Storage settings app
- `subghz` - Subghz application, 433 fobs and etc
- `system` - System settings, tools and API
- `tests` - Unit tests and etc
- `u2f` - U2F Application
- `application.c` - Firmware application list source
- `application.h` - Firmware application list header
- `application.mk` - Makefile helper

View File

@ -4,7 +4,7 @@
#include <gui/view_dispatcher.h>
#include <gui/modules/empty_screen.h>
#include <m-string.h>
#include <furi-hal-version.h>
#include <furi_hal_version.h>
typedef DialogMessageButton (*AboutDialogScreen)(DialogsApp* dialogs, DialogMessage* message);
@ -14,7 +14,7 @@ static DialogMessageButton product_screen(DialogsApp* dialogs, DialogMessage* me
const char* screen_header = "Product: Flipper Zero\n"
"Model: FZ.1\n";
const char* screen_text = "FCC ID: 2A2V6-FZ\n"
"IC ID: 27624-FZ";
"IC: 27624-FZ";
dialog_message_set_header(message, screen_header, 0, 0, AlignLeft, AlignTop);
dialog_message_set_text(message, screen_text, 0, 26, AlignLeft, AlignTop);
@ -222,4 +222,4 @@ int32_t about_settings_app(void* p) {
furi_record_close("gui");
return 0;
}
}

View File

@ -1,4 +1,4 @@
#include "accessor-app.h"
#include "accessor_app.h"
// app enter function
extern "C" int32_t accessor_app(void* p) {

View File

@ -1,6 +1,6 @@
#include "accessor-app.h"
#include "accessor_app.h"
#include <furi.h>
#include <furi-hal.h>
#include <furi_hal.h>
#include <stdarg.h>
void AccessorApp::run(void) {

View File

@ -1,15 +1,15 @@
#pragma once
#include <map>
#include <list>
#include "accessor-view-manager.h"
#include "accessor_view_manager.h"
#include "scene/accessor-scene-start.h"
#include "scene/accessor_scene_start.h"
#include "helpers/wiegand.h"
#include <one_wire_master.h>
#include <notification/notification-messages.h>
#include <notification/notification_messages.h>
class AccessorApp {
public:

View File

@ -1,5 +1,5 @@
#include "accessor-view-manager.h"
#include "accessor-event.h"
#include "accessor_view_manager.h"
#include "accessor_event.h"
#include <callback-connector.h>
AccessorAppViewManager::AccessorAppViewManager() {

View File

@ -3,7 +3,7 @@
#include <gui/view_dispatcher.h>
#include <gui/modules/submenu.h>
#include <gui/modules/popup.h>
#include "accessor-event.h"
#include "accessor_event.h"
class AccessorAppViewManager {
public:

View File

@ -1,6 +1,6 @@
#include "wiegand.h"
#include <furi.h>
#include <furi-hal.h>
#include <furi_hal.h>
volatile unsigned long WIEGAND::_cardTempHigh = 0;
volatile unsigned long WIEGAND::_cardTemp = 0;

View File

@ -1,5 +1,5 @@
#pragma once
#include "../accessor-app.h"
#include "../accessor_app.h"
class AccessorApp;

View File

@ -1,8 +1,8 @@
#include "../accessor-app.h"
#include "../accessor-view-manager.h"
#include "../accessor-event.h"
#include "../accessor_app.h"
#include "../accessor_view_manager.h"
#include "../accessor_event.h"
#include <callback-connector.h>
#include "accessor-scene-start.h"
#include "accessor_scene_start.h"
void AccessorSceneStart::on_enter(AccessorApp* app) {
AccessorAppViewManager* view_manager = app->get_view_manager();

View File

@ -1,5 +1,5 @@
#pragma once
#include "accessor-scene-generic.h"
#include "accessor_scene_generic.h"
class AccessorSceneStart : public AccessorScene {
public:

View File

@ -42,22 +42,23 @@ extern int32_t usb_mouse_app(void* p);
extern int32_t usb_test_app(void* p);
extern int32_t vibro_test_app(void* p);
extern int32_t bt_hid_app(void* p);
extern int32_t battery_test_app(void* p);
// Plugins
extern int32_t music_player_app(void* p);
extern int32_t snake_game_app(void* p);
// On system start hooks declaration
extern void bt_cli_init();
extern void crypto_cli_init();
extern void ibutton_cli_init();
extern void irda_cli_init();
extern void lfrfid_cli_init();
extern void nfc_cli_init();
extern void storage_cli_init();
extern void subghz_cli_init();
extern void power_cli_init();
extern void unit_tests_cli_init();
extern void bt_on_system_start();
extern void crypto_on_system_start();
extern void ibutton_on_system_start();
extern void irda_on_system_start();
extern void lfrfid_on_system_start();
extern void nfc_on_system_start();
extern void storage_on_system_start();
extern void subghz_on_system_start();
extern void power_on_system_start();
extern void unit_tests_on_system_start();
// Settings
extern int32_t notification_settings_app(void* p);
@ -166,44 +167,42 @@ const size_t FLIPPER_APPS_COUNT = sizeof(FLIPPER_APPS) / sizeof(FlipperApplicati
// On system start hooks
const FlipperOnStartHook FLIPPER_ON_SYSTEM_START[] = {
#ifdef SRV_CLI
crypto_cli_init,
#endif
crypto_on_system_start,
#ifdef APP_IRDA
irda_cli_init,
irda_on_system_start,
#endif
#ifdef APP_NFC
nfc_cli_init,
nfc_on_system_start,
#endif
#ifdef APP_SUBGHZ
subghz_cli_init,
subghz_on_system_start,
#endif
#ifdef APP_LF_RFID
lfrfid_cli_init,
lfrfid_on_system_start,
#endif
#ifdef APP_IBUTTON
ibutton_cli_init,
ibutton_on_system_start,
#endif
#ifdef SRV_BT
bt_cli_init,
bt_on_system_start,
#endif
#ifdef SRV_POWER
power_cli_init,
power_on_system_start,
#endif
#ifdef SRV_STORAGE
storage_cli_init,
storage_on_system_start,
#endif
#ifdef APP_UNIT_TESTS
unit_tests_cli_init,
unit_tests_on_system_start,
#endif
};
@ -280,6 +279,10 @@ const FlipperApplication FLIPPER_DEBUG_APPS[] = {
#ifdef APP_DISPLAY_TEST
{.app = display_test_app, .name = "Display Test", .stack_size = 1024, .icon = NULL},
#endif
#ifdef APP_BATTERY_TEST
{.app = battery_test_app, .name = "Battery Test", .stack_size = 1024, .icon = NULL},
#endif
};
const size_t FLIPPER_DEBUG_APPS_COUNT = sizeof(FLIPPER_DEBUG_APPS) / sizeof(FlipperApplication);

View File

@ -155,6 +155,11 @@ CFLAGS += -DAPP_DISPLAY_TEST
SRV_GUI = 1
endif
APP_BATTERY_TEST ?= 0
ifeq ($(APP_BATTERY_TEST), 1)
CFLAGS += -DAPP_BATTERY_TEST
SRV_GUI = 1
endif
APP_USB_MOUSE ?= 0
ifeq ($(APP_USB_MOUSE), 1)
@ -298,6 +303,7 @@ SRV_GUI ?= 0
ifeq ($(SRV_GUI), 1)
CFLAGS += -DSRV_GUI
SRV_INPUT = 1
SRV_NOTIFICATION = 1
endif

View File

@ -1,5 +1,5 @@
#pragma once
#include "file-worker.h"
#include "file_worker.h"
#define ARCHIVE_FAV_PATH "/any/favorites.txt"
#define ARCHIVE_FAV_TEMP_PATH "/any/favorites.tmp"

View File

@ -1,5 +1,5 @@
#pragma once
#include "file-worker.h"
#include "file_worker.h"
#define MAX_FILES 100 //temp

View File

@ -1,6 +1,6 @@
#include "bad_usb_app_i.h"
#include <furi.h>
#include <furi-hal.h>
#include <furi_hal.h>
static bool bad_usb_app_custom_event_callback(void* context, uint32_t event) {
furi_assert(context);

View File

@ -9,8 +9,8 @@
#include <gui/scene_manager.h>
#include <gui/modules/submenu.h>
#include <dialogs/dialogs.h>
#include <notification/notification-messages.h>
#include <gui/modules/variable-item-list.h>
#include <notification/notification_messages.h>
#include <gui/modules/variable_item_list.h>
#include "views/bad_usb_view.h"
#define BAD_USB_APP_PATH_FOLDER "/any/badusb"

View File

@ -1,9 +1,9 @@
#include <furi.h>
#include <furi-hal.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <input/input.h>
#include <lib/toolbox/args.h>
#include <furi-hal-usb-hid.h>
#include <furi_hal_usb_hid.h>
#include <storage/storage.h>
#include "bad_usb_script.h"
@ -97,13 +97,30 @@ static const DuckyKey ducky_keys[] = {
};
static const char ducky_cmd_comment[] = {"REM"};
static const char ducky_cmd_delay[] = {"DELAY"};
static const char ducky_cmd_string[] = {"STRING"};
static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY"};
static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY"};
static const char ducky_cmd_repeat[] = {"REPEAT"};
static const char ducky_cmd_delay[] = {"DELAY "};
static const char ducky_cmd_string[] = {"STRING "};
static const char ducky_cmd_defdelay_1[] = {"DEFAULT_DELAY "};
static const char ducky_cmd_defdelay_2[] = {"DEFAULTDELAY "};
static const char ducky_cmd_repeat[] = {"REPEAT "};
static bool ducky_get_number(char* param, uint32_t* val) {
static const char ducky_cmd_altchar[] = {"ALTCHAR "};
static const char ducky_cmd_altstr_1[] = {"ALTSTRING "};
static const char ducky_cmd_altstr_2[] = {"ALTCODE "};
static const uint8_t numpad_keys[10] = {
KEYPAD_0,
KEYPAD_1,
KEYPAD_2,
KEYPAD_3,
KEYPAD_4,
KEYPAD_5,
KEYPAD_6,
KEYPAD_7,
KEYPAD_8,
KEYPAD_9,
};
static bool ducky_get_number(const char* param, uint32_t* val) {
uint32_t value = 0;
if(sscanf(param, "%lu", &value) == 1) {
*val = value;
@ -112,7 +129,7 @@ static bool ducky_get_number(char* param, uint32_t* val) {
return false;
}
static uint32_t ducky_get_command_len(char* line) {
static uint32_t ducky_get_command_len(const char* line) {
uint32_t len = strlen(line);
for(uint32_t i = 0; i < len; i++) {
if(line[i] == ' ') return i;
@ -120,7 +137,64 @@ static uint32_t ducky_get_command_len(char* line) {
return 0;
}
static bool ducky_string(char* param) {
static void ducky_numlock_on() {
if((furi_hal_hid_get_led_state() & HID_KB_LED_NUM) == 0) {
furi_hal_hid_kb_press(KEY_NUM_LOCK);
furi_hal_hid_kb_release(KEY_NUM_LOCK);
}
}
static bool ducky_numpad_press(const char num) {
if((num < '0') || (num > '9')) return false;
uint16_t key = numpad_keys[num - '0'];
furi_hal_hid_kb_press(key);
furi_hal_hid_kb_release(key);
return true;
}
static bool ducky_altchar(const char* charcode) {
uint8_t i = 0;
bool state = false;
//TODO: numlock
FURI_LOG_I(WORKER_TAG, "char %s", charcode);
furi_hal_hid_kb_press(KEY_MOD_LEFT_ALT);
while((charcode[i] != ' ') && (charcode[i] != '\n') && (charcode[i] != '\0')) {
state = ducky_numpad_press(charcode[i]);
if(state == false) break;
i++;
}
furi_hal_hid_kb_release(KEY_MOD_LEFT_ALT);
return state;
}
static bool ducky_altstring(const char* param) {
uint32_t i = 0;
bool state = false;
while(param[i] != '\0') {
if((param[i] < ' ') || (param[i] > '~')) {
i++;
continue; // Skip non-printable chars
}
char temp_str[4];
snprintf(temp_str, 4, "%u", param[i]);
state = ducky_altchar(temp_str);
if(state == false) break;
i++;
}
return state;
}
static bool ducky_string(const char* param) {
uint32_t i = 0;
while(param[i] != '\0') {
furi_hal_hid_kb_press(HID_ASCII_TO_KEY(param[i]));
@ -130,10 +204,14 @@ static bool ducky_string(char* param) {
return true;
}
static uint16_t ducky_get_keycode(char* param, bool accept_chars) {
static uint16_t ducky_get_keycode(const char* param, bool accept_chars) {
for(uint8_t i = 0; i < (sizeof(ducky_keys) / sizeof(ducky_keys[0])); i++) {
if(strncmp(param, ducky_keys[i].name, strlen(ducky_keys[i].name)) == 0)
uint8_t key_cmd_len = strlen(ducky_keys[i].name);
if((strncmp(param, ducky_keys[i].name, key_cmd_len) == 0) &&
((param[key_cmd_len] == ' ') || (param[key_cmd_len] == '\n') ||
(param[key_cmd_len] == '\0'))) {
return ducky_keys[i].keycode;
}
}
if((accept_chars) && (strlen(param) > 0)) {
return (HID_ASCII_TO_KEY(param[0]) & 0xFF);
@ -143,57 +221,71 @@ static uint16_t ducky_get_keycode(char* param, bool accept_chars) {
static int32_t ducky_parse_line(BadUsbScript* bad_usb, string_t line) {
uint32_t line_len = string_size(line);
char* line_t = (char*)string_get_cstr(line);
const char* line_tmp = string_get_cstr(line);
bool state = false;
for(uint32_t i = 0; i < line_len; i++) {
if((line_t[i] != ' ') && (line_t[i] != '\t') && (line_t[i] != '\n')) {
line_t = &line_t[i];
if((line_tmp[i] != ' ') && (line_tmp[i] != '\t') && (line_tmp[i] != '\n')) {
line_tmp = &line_tmp[i];
break; // Skip spaces and tabs
}
if(i == line_len - 1) return 0; // Skip empty lines
}
FURI_LOG_I(WORKER_TAG, "line:%s", line_t);
FURI_LOG_I(WORKER_TAG, "line:%s", line_tmp);
// General commands
if(strncmp(line_t, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) {
if(strncmp(line_tmp, ducky_cmd_comment, strlen(ducky_cmd_comment)) == 0) {
// REM - comment line
return (0);
} else if(strncmp(line_t, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) {
} else if(strncmp(line_tmp, ducky_cmd_delay, strlen(ducky_cmd_delay)) == 0) {
// DELAY
line_t = &line_t[ducky_get_command_len(line_t) + 1];
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
uint32_t delay_val = 0;
state = ducky_get_number(line_t, &delay_val);
state = ducky_get_number(line_tmp, &delay_val);
if((state) && (delay_val > 0)) {
return (int32_t)delay_val;
}
return (-1);
} else if(
(strncmp(line_t, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) ||
(strncmp(line_t, ducky_cmd_defdelay_2, strlen(ducky_cmd_defdelay_2)) == 0)) {
(strncmp(line_tmp, ducky_cmd_defdelay_1, strlen(ducky_cmd_defdelay_1)) == 0) ||
(strncmp(line_tmp, ducky_cmd_defdelay_2, strlen(ducky_cmd_defdelay_2)) == 0)) {
// DEFAULT_DELAY
line_t = &line_t[ducky_get_command_len(line_t) + 1];
state = ducky_get_number(line_t, &bad_usb->defdelay);
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
state = ducky_get_number(line_tmp, &bad_usb->defdelay);
return (state) ? (0) : (-1);
} else if(strncmp(line_t, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) {
} else if(strncmp(line_tmp, ducky_cmd_string, strlen(ducky_cmd_string)) == 0) {
// STRING
line_t = &line_t[ducky_get_command_len(line_t) + 1];
state = ducky_string(line_t);
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
state = ducky_string(line_tmp);
return (state) ? (0) : (-1);
} else if(strncmp(line_t, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) {
} else if(strncmp(line_tmp, ducky_cmd_altchar, strlen(ducky_cmd_altchar)) == 0) {
// ALTCHAR
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
ducky_numlock_on();
state = ducky_altchar(line_tmp);
return (state) ? (0) : (-1);
} else if(
(strncmp(line_tmp, ducky_cmd_altstr_1, strlen(ducky_cmd_altstr_1)) == 0) ||
(strncmp(line_tmp, ducky_cmd_altstr_2, strlen(ducky_cmd_altstr_2)) == 0)) {
// ALTSTRING
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
ducky_numlock_on();
state = ducky_altstring(line_tmp);
return (state) ? (0) : (-1);
} else if(strncmp(line_tmp, ducky_cmd_repeat, strlen(ducky_cmd_repeat)) == 0) {
// REPEAT
line_t = &line_t[ducky_get_command_len(line_t) + 1];
state = ducky_get_number(line_t, &bad_usb->repeat_cnt);
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
state = ducky_get_number(line_tmp, &bad_usb->repeat_cnt);
return (state) ? (0) : (-1);
} else {
// Special keys + modifiers
uint16_t key = ducky_get_keycode(line_t, false);
uint16_t key = ducky_get_keycode(line_tmp, false);
if(key == KEY_NONE) return (-1);
if((key & 0xFF00) != 0) {
// It's a modifier key
line_t = &line_t[ducky_get_command_len(line_t) + 1];
key |= ducky_get_keycode(line_t, true);
line_tmp = &line_tmp[ducky_get_command_len(line_tmp) + 1];
key |= ducky_get_keycode(line_tmp, true);
}
furi_hal_hid_kb_press(key);
furi_hal_hid_kb_release(key);

View File

@ -1,5 +1,5 @@
#include "../bad_usb_app_i.h"
#include "furi-hal-power.h"
#include "furi_hal_power.h"
static bool bad_usb_file_select(BadUsbApp* bad_usb) {
furi_assert(bad_usb);

View File

@ -1,7 +1,7 @@
#include "../bad_usb_script.h"
#include "../bad_usb_app_i.h"
#include "../views/bad_usb_view.h"
#include "furi-hal.h"
#include "furi_hal.h"
void bad_usb_scene_work_ok_callback(InputType type, void* context) {
furi_assert(context);

View File

@ -1,21 +1,18 @@
#include "bt_cli.h"
#include <furi.h>
#include <furi-hal.h>
#include <furi_hal.h>
#include <applications/cli/cli.h>
#include <lib/toolbox/args.h>
#include "bt_settings.h"
void bt_cli_init() {
Cli* cli = furi_record_open("cli");
static const char* bt_cli_address_types[] = {
"Public Device Address",
"Random Device Address",
"Public Identity Address",
"Random (Static) Identity Address",
};
cli_add_command(cli, "bt_info", CliCommandFlagDefault, bt_cli_command_info, NULL);
cli_add_command(cli, "bt_tx_carrier", CliCommandFlagDefault, bt_cli_command_carrier_tx, NULL);
cli_add_command(cli, "bt_rx_carrier", CliCommandFlagDefault, bt_cli_command_carrier_rx, NULL);
cli_add_command(cli, "bt_tx_pt", CliCommandFlagDefault, bt_cli_command_packet_tx, NULL);
cli_add_command(cli, "bt_rx_pt", CliCommandFlagDefault, bt_cli_command_packet_rx, NULL);
furi_record_close("cli");
}
void bt_cli_command_info(Cli* cli, string_t args, void* context) {
static void bt_cli_command_hci_info(Cli* cli, string_t args, void* context) {
string_t buffer;
string_init(buffer);
furi_hal_bt_dump_state(buffer);
@ -23,160 +20,229 @@ void bt_cli_command_info(Cli* cli, string_t args, void* context) {
string_clear(buffer);
}
void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) {
uint16_t channel;
uint16_t power;
BtSettings bt_settings;
bt_settings_load(&bt_settings);
static void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context) {
int channel = 0;
int power = 0;
int ret = sscanf(string_get_cstr(args), "%hu %hu", &channel, &power);
if(ret != 2) {
printf("sscanf returned %d, channel: %hu, power: %hu\r\n", ret, channel, power);
cli_print_usage("bt_tx_carrier", "<Channel number> <Power>", string_get_cstr(args));
return;
}
if(channel > 39) {
printf("Channel number must be in 0...39 range, not %hu\r\n", channel);
return;
}
if(power > 6) {
printf("Power must be in 0...6 dB range, not %hu\r\n", power);
return;
}
do {
if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) {
printf("Incorrect or missing channel, expected int 0-39");
break;
}
if(!args_read_int_and_trim(args, &power) && (power < 0 || power > 6)) {
printf("Incorrect or missing power, expected int 0-6");
break;
}
furi_hal_bt_stop_advertising();
printf("Transmitting carrier at %hu channel at %hu dB power\r\n", channel, power);
printf("Press CTRL+C to stop\r\n");
furi_hal_bt_start_tone_tx(channel, 0x19 + power);
furi_hal_bt_stop_advertising();
printf("Transmitting carrier at %d channel at %d dB power\r\n", channel, power);
printf("Press CTRL+C to stop\r\n");
furi_hal_bt_start_tone_tx(channel, 0x19 + power);
while(!cli_cmd_interrupt_received(cli)) {
osDelay(250);
while(!cli_cmd_interrupt_received(cli)) {
osDelay(250);
}
furi_hal_bt_stop_tone_tx();
} while(false);
}
static void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) {
int channel = 0;
do {
if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) {
printf("Incorrect or missing channel, expected int 0-39");
break;
}
furi_hal_bt_stop_advertising();
printf("Receiving carrier at %d channel\r\n", channel);
printf("Press CTRL+C to stop\r\n");
furi_hal_bt_start_packet_rx(channel, 1);
while(!cli_cmd_interrupt_received(cli)) {
osDelay(250);
printf("RSSI: %6.1f dB\r", furi_hal_bt_get_rssi());
fflush(stdout);
}
furi_hal_bt_stop_packet_test();
} while(false);
}
static void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) {
int channel = 0;
int pattern = 0;
int datarate = 1;
do {
if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) {
printf("Incorrect or missing channel, expected int 0-39");
break;
}
if(!args_read_int_and_trim(args, &pattern) && (pattern < 0 || pattern > 5)) {
printf("Incorrect or missing pattern, expected int 0-5 \r\n");
printf("0 - Pseudo-Random bit sequence 9\r\n");
printf("1 - Pattern of alternating bits '11110000'\r\n");
printf("2 - Pattern of alternating bits '10101010'\r\n");
printf("3 - Pseudo-Random bit sequence 15\r\n");
printf("4 - Pattern of All '1' bits\r\n");
printf("5 - Pattern of All '0' bits\r\n");
break;
}
if(!args_read_int_and_trim(args, &datarate) && (datarate < 1 || datarate > 2)) {
printf("Incorrect or missing datarate, expected int 1-2");
break;
}
furi_hal_bt_stop_advertising();
printf(
"Transmitting %d pattern packet at %d channel at %d M datarate\r\n",
pattern,
channel,
datarate);
printf("Press CTRL+C to stop\r\n");
furi_hal_bt_start_packet_tx(channel, pattern, datarate);
while(!cli_cmd_interrupt_received(cli)) {
osDelay(250);
}
furi_hal_bt_stop_packet_test();
printf("Transmitted %lu packets", furi_hal_bt_get_transmitted_packets());
} while(false);
}
static void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) {
int channel = 0;
int datarate = 1;
do {
if(!args_read_int_and_trim(args, &channel) && (channel < 0 || channel > 39)) {
printf("Incorrect or missing channel, expected int 0-39");
break;
}
if(!args_read_int_and_trim(args, &datarate) && (datarate < 1 || datarate > 2)) {
printf("Incorrect or missing datarate, expected int 1-2");
break;
}
furi_hal_bt_stop_advertising();
printf("Receiving packets at %d channel at %d M datarate\r\n", channel, datarate);
printf("Press CTRL+C to stop\r\n");
furi_hal_bt_start_packet_rx(channel, datarate);
float rssi_raw = 0;
while(!cli_cmd_interrupt_received(cli)) {
osDelay(250);
rssi_raw = furi_hal_bt_get_rssi();
printf("RSSI: %03.1f dB\r", rssi_raw);
fflush(stdout);
}
uint16_t packets_received = furi_hal_bt_stop_packet_test();
printf("Received %hu packets", packets_received);
} while(false);
}
static void bt_cli_scan_callback(GapAddress address, void* context) {
furi_assert(context);
osMessageQueueId_t queue = context;
osMessageQueuePut(queue, &address, NULL, 250);
}
static void bt_cli_command_scan(Cli* cli, string_t args, void* context) {
osMessageQueueId_t queue = osMessageQueueNew(20, sizeof(GapAddress), NULL);
furi_hal_bt_start_scan(bt_cli_scan_callback, queue);
GapAddress address = {};
bool exit = false;
while(!exit) {
if(osMessageQueueGet(queue, &address, NULL, 250) == osOK) {
if(address.type < sizeof(bt_cli_address_types)) {
printf("Found new device. Type: %s, MAC: ", bt_cli_address_types[address.type]);
for(uint8_t i = 0; i < sizeof(address.mac) - 1; i++) {
printf("%02X:", address.mac[i]);
}
printf("%02X\r\n", address.mac[sizeof(address.mac) - 1]);
}
}
exit = cli_cmd_interrupt_received(cli);
}
furi_hal_bt_stop_tone_tx();
if(bt_settings.enabled) {
furi_hal_bt_start_advertising();
furi_hal_bt_stop_scan();
osMessageQueueDelete(queue);
}
static void bt_cli_print_usage() {
printf("Usage:\r\n");
printf("bt <cmd> <args>\r\n");
printf("Cmd list:\r\n");
printf("\thci_info\t - HCI info\r\n");
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) &&
furi_hal_bt_get_radio_stack() == FuriHalBtStackHciLayer) {
printf("\ttx_carrier <channel:0-39> <power:0-6>\t - start tx carrier test\r\n");
printf("\trx_carrier <channel:0-39>\t - start rx carrier test\r\n");
printf("\ttx_pt <channel:0-39> <pattern:0-5> <datarate:1-2>\t - start tx packet test\r\n");
printf("\trx_pt <channel:0-39> <datarate:1-2>\t - start rx packer test\r\n");
printf("\tscan\t - start scanner\r\n");
}
}
void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context) {
uint16_t channel;
static void bt_cli(Cli* cli, string_t args, void* context) {
string_t cmd;
string_init(cmd);
BtSettings bt_settings;
bt_settings_load(&bt_settings);
int ret = sscanf(string_get_cstr(args), "%hu", &channel);
if(ret != 1) {
printf("sscanf returned %d, channel: %hu\r\n", ret, channel);
cli_print_usage("bt_rx_carrier", "<Channel number>", string_get_cstr(args));
return;
}
if(channel > 39) {
printf("Channel number must be in 0...39 range, not %hu\r\n", channel);
return;
}
furi_hal_bt_stop_advertising();
printf("Receiving carrier at %hu channel\r\n", channel);
printf("Press CTRL+C to stop\r\n");
do {
if(!args_read_string_and_trim(args, cmd)) {
bt_cli_print_usage();
break;
}
if(string_cmp_str(cmd, "hci_info") == 0) {
bt_cli_command_hci_info(cli, args, NULL);
break;
}
if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug) &&
furi_hal_bt_get_radio_stack() == FuriHalBtStackHciLayer) {
if(string_cmp_str(cmd, "carrier_tx") == 0) {
bt_cli_command_carrier_tx(cli, args, NULL);
break;
}
if(string_cmp_str(cmd, "carrier_rx") == 0) {
bt_cli_command_carrier_rx(cli, args, NULL);
break;
}
if(string_cmp_str(cmd, "packet_tx") == 0) {
bt_cli_command_packet_tx(cli, args, NULL);
break;
}
if(string_cmp_str(cmd, "packet_rx") == 0) {
bt_cli_command_packet_rx(cli, args, NULL);
break;
}
if(string_cmp_str(cmd, "scan") == 0) {
bt_cli_command_scan(cli, args, NULL);
break;
}
}
furi_hal_bt_start_packet_rx(channel, 1);
bt_cli_print_usage();
} while(false);
while(!cli_cmd_interrupt_received(cli)) {
osDelay(1024 / 4);
printf("RSSI: %6.1f dB\r", furi_hal_bt_get_rssi());
fflush(stdout);
}
furi_hal_bt_stop_packet_test();
if(bt_settings.enabled) {
furi_hal_bt_start_advertising();
}
string_clear(cmd);
}
void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context) {
uint16_t channel;
uint16_t pattern;
uint16_t datarate;
BtSettings bt_settings;
bt_settings_load(&bt_settings);
int ret = sscanf(string_get_cstr(args), "%hu %hu %hu", &channel, &pattern, &datarate);
if(ret != 3) {
printf("sscanf returned %d, channel: %hu %hu %hu\r\n", ret, channel, pattern, datarate);
cli_print_usage(
"bt_tx_pt", "<Channel number> <Pattern> <Datarate>", string_get_cstr(args));
return;
}
if(channel > 39) {
printf("Channel number must be in 0...39 range, not %hu\r\n", channel);
return;
}
if(pattern > 5) {
printf("Pattern must be in 0...5 range, not %hu\r\n", pattern);
printf("0 - Pseudo-Random bit sequence 9\r\n");
printf("1 - Pattern of alternating bits '11110000'\r\n");
printf("2 - Pattern of alternating bits '10101010'\r\n");
printf("3 - Pseudo-Random bit sequence 15\r\n");
printf("4 - Pattern of All '1' bits\r\n");
printf("5 - Pattern of All '0' bits\r\n");
return;
}
if(datarate < 1 || datarate > 2) {
printf("Datarate must be in 1 or 2 Mb, not %hu\r\n", datarate);
return;
}
furi_hal_bt_stop_advertising();
printf(
"Transmitting %hu pattern packet at %hu channel at %hu M datarate\r\n",
pattern,
channel,
datarate);
printf("Press CTRL+C to stop\r\n");
furi_hal_bt_start_packet_tx(channel, pattern, datarate);
while(!cli_cmd_interrupt_received(cli)) {
osDelay(250);
}
furi_hal_bt_stop_packet_test();
printf("Transmitted %lu packets", furi_hal_bt_get_transmitted_packets());
if(bt_settings.enabled) {
furi_hal_bt_start_advertising();
}
}
void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context) {
uint16_t channel;
uint16_t datarate;
BtSettings bt_settings;
bt_settings_load(&bt_settings);
int ret = sscanf(string_get_cstr(args), "%hu %hu", &channel, &datarate);
if(ret != 2) {
printf("sscanf returned %d, channel: %hu datarate: %hu\r\n", ret, channel, datarate);
cli_print_usage("bt_rx_pt", "<Channel number> <Datarate>", string_get_cstr(args));
return;
}
if(channel > 39) {
printf("Channel number must be in 0...39 range, not %hu\r\n", channel);
return;
}
if(datarate < 1 || datarate > 2) {
printf("Datarate must be in 1 or 2 Mb, not %hu\r\n", datarate);
return;
}
furi_hal_bt_stop_advertising();
printf("Receiving packets at %hu channel at %hu M datarate\r\n", channel, datarate);
printf("Press CTRL+C to stop\r\n");
furi_hal_bt_start_packet_rx(channel, datarate);
float rssi_raw = 0;
while(!cli_cmd_interrupt_received(cli)) {
osDelay(250);
rssi_raw = furi_hal_bt_get_rssi();
printf("RSSI: %03.1f dB\r", rssi_raw);
fflush(stdout);
}
uint16_t packets_received = furi_hal_bt_stop_packet_test();
printf("Received %hu packets", packets_received);
if(bt_settings.enabled) {
furi_hal_bt_start_advertising();
}
void bt_on_system_start() {
#ifdef SRV_CLI
Cli* cli = furi_record_open("cli");
furi_record_open("bt");
cli_add_command(cli, "bt", CliCommandFlagDefault, bt_cli, NULL);
furi_record_close("bt");
furi_record_close("cli");
#endif
}

View File

@ -1,15 +0,0 @@
#pragma once
#include <cli/cli.h>
void bt_cli_init();
void bt_cli_command_info(Cli* cli, string_t args, void* context);
void bt_cli_command_carrier_tx(Cli* cli, string_t args, void* context);
void bt_cli_command_carrier_rx(Cli* cli, string_t args, void* context);
void bt_cli_command_packet_tx(Cli* cli, string_t args, void* context);
void bt_cli_command_packet_rx(Cli* cli, string_t args, void* context);

11
applications/bt/bt_debug_app/bt_debug_app.c Executable file → Normal file
View File

@ -1,5 +1,7 @@
#include "bt_debug_app.h"
#include <furi-hal-bt.h>
#include <furi_hal_bt.h>
#define TAG "BtDebugApp"
enum BtDebugSubmenuIndex {
BtDebugSubmenuIndexCarrierTest,
@ -92,6 +94,13 @@ void bt_debug_app_free(BtDebugApp* app) {
}
int32_t bt_debug_app(void* p) {
if(furi_hal_bt_get_radio_stack() != FuriHalBtStackHciLayer) {
FURI_LOG_E(TAG, "Incorrect radio stack, replace with HciLayer for tests.");
DialogsApp* dialogs = furi_record_open("dialogs");
dialog_message_show_storage_error(dialogs, "Incorrect\nRadioStack");
return 255;
}
BtDebugApp* app = bt_debug_app_alloc();
// Stop advertising
furi_hal_bt_stop_advertising();

View File

@ -4,6 +4,7 @@
#include <gui/gui.h>
#include <gui/view.h>
#include <gui/view_dispatcher.h>
#include <dialogs/dialogs.h>
#include <gui/modules/submenu.h>
#include "views/bt_carrier_test.h"

View File

@ -1,7 +1,7 @@
#include "bt_carrier_test.h"
#include "bt_test.h"
#include "bt_test_types.h"
#include "furi-hal-bt.h"
#include "furi_hal_bt.h"
struct BtCarrierTest {
BtTest* bt_test;

View File

@ -1,7 +1,7 @@
#include "bt_packet_test.h"
#include "bt_test.h"
#include "bt_test_types.h"
#include "furi-hal-bt.h"
#include "furi_hal_bt.h"
struct BtPacketTest {
BtTest* bt_test;

View File

@ -1,6 +1,6 @@
#include "bt_hid.h"
#include <furi-hal-bt.h>
#include <applications/notification/notification-messages.h>
#include <furi_hal_bt.h>
#include <applications/notification/notification_messages.h>
#define TAG "BtHidApp"

View File

@ -1,7 +1,7 @@
#include "bt_hid_keynote.h"
#include <furi.h>
#include <furi-hal-bt-hid.h>
#include <furi-hal-usb-hid.h>
#include <furi_hal_bt_hid.h>
#include <furi_hal_usb_hid.h>
#include <gui/elements.h>
struct BtHidKeynote {

View File

@ -1,7 +1,7 @@
#include "bt_hid_media.h"
#include <furi.h>
#include <furi-hal-bt-hid.h>
#include <furi-hal-usb-hid.h>
#include <furi_hal_bt_hid.h>
#include <furi_hal_usb_hid.h>
#include <gui/elements.h>
struct BtHidMedia {

View File

@ -2,7 +2,7 @@
#include "battery_service.h"
#include "bt_keys_storage.h"
#include <applications/notification/notification-messages.h>
#include <applications/notification/notification_messages.h>
#define TAG "BtSrv"
@ -154,12 +154,12 @@ static void bt_rpc_send_bytes_callback(void* context, uint8_t* bytes, size_t byt
}
// Called from GAP thread
static bool bt_on_gap_event_callback(BleEvent event, void* context) {
static bool bt_on_gap_event_callback(GapEvent event, void* context) {
furi_assert(context);
Bt* bt = context;
bool ret = false;
if(event.type == BleEventTypeConnected) {
if(event.type == GapEventTypeConnected) {
// Update status bar
bt->status = BtStatusConnected;
BtMessage message = {.type = BtMessageTypeUpdateStatusbar};
@ -181,7 +181,7 @@ static bool bt_on_gap_event_callback(BleEvent event, void* context) {
message.data.battery_level = info.charge;
furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
ret = true;
} else if(event.type == BleEventTypeDisconnected) {
} else if(event.type == GapEventTypeDisconnected) {
if(bt->profile == BtProfileSerial && bt->rpc_session) {
FURI_LOG_I(TAG, "Close RPC connection");
osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED);
@ -190,24 +190,24 @@ static bool bt_on_gap_event_callback(BleEvent event, void* context) {
bt->rpc_session = NULL;
}
ret = true;
} else if(event.type == BleEventTypeStartAdvertising) {
} else if(event.type == GapEventTypeStartAdvertising) {
bt->status = BtStatusAdvertising;
BtMessage message = {.type = BtMessageTypeUpdateStatusbar};
furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
ret = true;
} else if(event.type == BleEventTypeStopAdvertising) {
} else if(event.type == GapEventTypeStopAdvertising) {
bt->status = BtStatusOff;
BtMessage message = {.type = BtMessageTypeUpdateStatusbar};
furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
ret = true;
} else if(event.type == BleEventTypePinCodeShow) {
} else if(event.type == GapEventTypePinCodeShow) {
BtMessage message = {
.type = BtMessageTypePinCodeShow, .data.pin_code = event.data.pin_code};
furi_check(osMessageQueuePut(bt->message_queue, &message, 0, osWaitForever) == osOK);
ret = true;
} else if(event.type == BleEventTypePinCodeVerify) {
} else if(event.type == GapEventTypePinCodeVerify) {
ret = bt_pin_code_verify_event_handler(bt, event.data.pin_code);
} else if(event.type == BleEventTypeUpdateMTU) {
} else if(event.type == GapEventTypeUpdateMTU) {
bt->max_packet_size = event.data.max_packet_size;
ret = true;
}
@ -234,33 +234,45 @@ static void bt_statusbar_update(Bt* bt) {
}
}
static void bt_show_warning(Bt* bt, const char* text) {
dialog_message_set_text(bt->dialog_message, text, 64, 28, AlignCenter, AlignCenter);
dialog_message_set_buttons(bt->dialog_message, "Quit", NULL, NULL);
dialog_message_show(bt->dialogs, bt->dialog_message);
}
static void bt_change_profile(Bt* bt, BtMessage* message) {
bt_settings_load(&bt->bt_settings);
if(bt->profile == BtProfileSerial && bt->rpc_session) {
FURI_LOG_I(TAG, "Close RPC connection");
osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED);
rpc_session_close(bt->rpc_session);
furi_hal_bt_serial_set_event_callback(0, NULL, NULL);
bt->rpc_session = NULL;
}
FuriHalBtProfile furi_profile;
if(message->data.profile == BtProfileHidKeyboard) {
furi_profile = FuriHalBtProfileHidKeyboard;
} else {
furi_profile = FuriHalBtProfileSerial;
}
if(furi_hal_bt_change_app(furi_profile, bt_on_gap_event_callback, bt)) {
FURI_LOG_I(TAG, "Bt App started");
if(bt->bt_settings.enabled) {
furi_hal_bt_start_advertising();
FuriHalBtStack stack = furi_hal_bt_get_radio_stack();
if(stack == FuriHalBtStackLight) {
bt_settings_load(&bt->bt_settings);
if(bt->profile == BtProfileSerial && bt->rpc_session) {
FURI_LOG_I(TAG, "Close RPC connection");
osEventFlagsSet(bt->rpc_event, BT_RPC_EVENT_DISCONNECTED);
rpc_session_close(bt->rpc_session);
furi_hal_bt_serial_set_event_callback(0, NULL, NULL);
bt->rpc_session = NULL;
}
FuriHalBtProfile furi_profile;
if(message->data.profile == BtProfileHidKeyboard) {
furi_profile = FuriHalBtProfileHidKeyboard;
} else {
furi_profile = FuriHalBtProfileSerial;
}
if(furi_hal_bt_change_app(furi_profile, bt_on_gap_event_callback, bt)) {
FURI_LOG_I(TAG, "Bt App started");
if(bt->bt_settings.enabled) {
furi_hal_bt_start_advertising();
}
furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt);
bt->profile = message->data.profile;
*message->result = true;
} else {
FURI_LOG_E(TAG, "Failed to start Bt App");
*message->result = false;
}
furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt);
bt->profile = message->data.profile;
*message->result = true;
} else {
FURI_LOG_E(TAG, "Failed to start Bt App");
bt_show_warning(bt, "Radio stack doesn't support this app");
*message->result = false;
}
osEventFlagsSet(bt->api_event, BT_API_UNLOCK_EVENT);
@ -268,26 +280,35 @@ static void bt_change_profile(Bt* bt, BtMessage* message) {
int32_t bt_srv() {
Bt* bt = bt_alloc();
furi_record_create("bt", bt);
// Read keys
if(!bt_load_key_storage(bt)) {
FURI_LOG_W(TAG, "Failed to load bonding keys");
}
// Start BLE stack
if(furi_hal_bt_start_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt)) {
FURI_LOG_I(TAG, "BLE stack started");
if(bt->bt_settings.enabled) {
furi_hal_bt_start_advertising();
// Start radio stack
if(!furi_hal_bt_start_radio_stack()) {
FURI_LOG_E(TAG, "Radio stack start failed");
}
FuriHalBtStack stack_type = furi_hal_bt_get_radio_stack();
if(stack_type == FuriHalBtStackUnknown) {
bt_show_warning(bt, "Unsupported radio stack");
bt->status = BtStatusUnavailable;
} else if(stack_type == FuriHalBtStackHciLayer) {
bt->status = BtStatusUnavailable;
} else if(stack_type == FuriHalBtStackLight) {
if(!furi_hal_bt_start_app(FuriHalBtProfileSerial, bt_on_gap_event_callback, bt)) {
FURI_LOG_E(TAG, "BLE App start failed");
} else {
if(bt->bt_settings.enabled) {
furi_hal_bt_start_advertising();
}
furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt);
}
furi_hal_bt_set_key_storage_change_callback(bt_on_key_storage_change_callback, bt);
} else {
FURI_LOG_E(TAG, "BT App start failed");
}
// Update statusbar
bt_statusbar_update(bt);
furi_record_create("bt", bt);
BtMessage message;
while(1) {

View File

@ -10,6 +10,7 @@ extern "C" {
typedef struct Bt Bt;
typedef enum {
BtStatusUnavailable,
BtStatusOff,
BtStatusAdvertising,
BtStatusConnected,

View File

@ -3,7 +3,7 @@
#include "bt.h"
#include <furi.h>
#include <furi-hal.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/view_port.h>

View File

@ -1,6 +1,6 @@
#include "bt_keys_storage.h"
#include <furi.h>
#include <file-worker.h>
#include <file_worker.h>
#define BT_KEYS_STORAGE_TAG "bt keys storage"
#define BT_KEYS_STORAGE_PATH "/int/bt.keys"

View File

@ -1,6 +1,6 @@
#include "bt_settings.h"
#include <furi.h>
#include <file-worker.h>
#include <file_worker.h>
#define TAG "BtSettings"
#define BT_SETTINGS_PATH "/int/bt.settings"

View File

@ -6,7 +6,7 @@
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/variable-item-list.h>
#include <gui/modules/variable_item_list.h>
#include "../bt_settings.h"
#include "scenes/bt_settings_scene.h"

View File

@ -1,5 +1,5 @@
#include "../bt_settings_app.h"
#include "furi-hal-bt.h"
#include "furi_hal_bt.h"
enum BtSetting {
BtSettingOff,
@ -23,20 +23,26 @@ static void bt_settings_scene_start_var_list_change_callback(VariableItem* item)
void bt_settings_scene_start_on_enter(void* context) {
BtSettingsApp* app = context;
VariableItemList* var_item_list = app->var_item_list;
VariableItem* item;
item = variable_item_list_add(
var_item_list,
"Bluetooth",
BtSettingNum,
bt_settings_scene_start_var_list_change_callback,
app);
if(app->settings.enabled) {
variable_item_set_current_value_index(item, BtSettingOn);
variable_item_set_current_value_text(item, bt_settings_text[BtSettingOn]);
FuriHalBtStack stack_type = furi_hal_bt_get_radio_stack();
if(stack_type == FuriHalBtStackLight) {
item = variable_item_list_add(
var_item_list,
"Bluetooth",
BtSettingNum,
bt_settings_scene_start_var_list_change_callback,
app);
if(app->settings.enabled) {
variable_item_set_current_value_index(item, BtSettingOn);
variable_item_set_current_value_text(item, bt_settings_text[BtSettingOn]);
} else {
variable_item_set_current_value_index(item, BtSettingOff);
variable_item_set_current_value_text(item, bt_settings_text[BtSettingOff]);
}
} else {
variable_item_set_current_value_index(item, BtSettingOff);
variable_item_set_current_value_text(item, bt_settings_text[BtSettingOff]);
item = variable_item_list_add(var_item_list, "Bluetooth", 1, NULL, NULL);
variable_item_set_current_value_text(item, "Broken");
}
view_dispatcher_switch_to_view(app->view_dispatcher, BtSettingsAppViewVarItemList);

View File

@ -1,7 +1,7 @@
#include "cli_i.h"
#include "cli_commands.h"
#include <furi-hal-version.h>
#include <furi_hal_version.h>
#include <loader/loader.h>
Cli* cli_alloc() {

View File

@ -1,10 +1,10 @@
#include "cli_commands.h"
#include <furi-hal.h>
#include <furi-hal-gpio.h>
#include <furi-hal-info.h>
#include <task-control-block.h>
#include <furi_hal.h>
#include <furi_hal_gpio.h>
#include <furi_hal_info.h>
#include <task_control_block.h>
#include <time.h>
#include <notification/notification-messages.h>
#include <notification/notification_messages.h>
// Close to ISO, `date +'%Y-%m-%d %H:%M:%S %u'`
#define CLI_DATE_FORMAT "%.4d-%.2d-%.2d %.2d:%.2d:%.2d %d"
@ -322,7 +322,6 @@ void cli_command_free_blocks(Cli* cli, string_t args, void* context) {
void cli_command_i2c(Cli* cli, string_t args, void* context) {
furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
uint8_t test = 0;
printf("Scanning external i2c on PC0(SCL)/PC1(SDA)\r\n"
"Clock: 100khz, 7bit address\r\n"
"\r\n");
@ -331,8 +330,8 @@ void cli_command_i2c(Cli* cli, string_t args, void* context) {
for(uint8_t row = 0; row < 0x8; row++) {
printf("%x | ", row);
for(uint8_t column = 0; column <= 0xF; column++) {
bool ret = furi_hal_i2c_rx(
&furi_hal_i2c_handle_external, ((row << 4) + column) << 1, &test, 1, 2);
bool ret = furi_hal_i2c_is_device_ready(
&furi_hal_i2c_handle_external, ((row << 4) + column) << 1, 2);
printf("%c ", ret ? '#' : '-');
}
printf("\r\n");

View File

@ -3,7 +3,7 @@
#include "cli.h"
#include <furi.h>
#include <furi-hal.h>
#include <furi_hal.h>
#include <m-dict.h>
#include <m-bptree.h>

View File

@ -1,4 +1,4 @@
#include <furi-hal.h>
#include <furi_hal.h>
#include <furi.h>
#include <lib/toolbox/args.h>
@ -312,8 +312,10 @@ void crypto_cli(Cli* cli, string_t args, void* context) {
string_clear(cmd);
}
void crypto_cli_init() {
void crypto_on_system_start() {
#ifdef SRV_CLI
Cli* cli = furi_record_open("cli");
cli_add_command(cli, "crypto", CliCommandFlagDefault, crypto_cli, NULL);
furi_record_close("cli");
#endif
}

View File

@ -1,10 +1,10 @@
#include <furi.h>
#include <furi-hal.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <input/input.h>
#include <notification/notification-messages.h>
#include <notification/notification_messages.h>
#define BLINK_COLOR_COUNT 7

View File

@ -1,6 +1,6 @@
#include "display_test.h"
#include <furi-hal.h>
#include <furi_hal.h>
#include <furi.h>
// Need access to u8g2
@ -10,7 +10,7 @@
#include <gui/view_dispatcher.h>
#include <gui/modules/submenu.h>
#include <gui/modules/variable-item-list.h>
#include <gui/modules/variable_item_list.h>
#include "view_display_test.h"

View File

@ -2,11 +2,11 @@
#include <m-string.h>
#include <gui/gui.h>
#include <notification/notification.h>
#include <notification/notification-messages.h>
#include <notification/notification_messages.h>
#include <gui/elements.h>
#include <stream_buffer.h>
#include <furi-hal-uart.h>
#include <furi-hal-console.h>
#include <furi_hal_uart.h>
#include <furi_hal_console.h>
#include <gui/view_dispatcher.h>
#include <gui/modules/dialog_ex.h>

View File

@ -1,5 +1,5 @@
#include <furi.h>
#include <furi-hal.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <input/input.h>

View File

@ -1,5 +1,5 @@
#include <furi.h>
#include <furi-hal.h>
#include <furi_hal.h>
#include <gui/view.h>
#include <gui/view_dispatcher.h>

View File

@ -1,9 +1,9 @@
#include <furi.h>
#include <furi-hal.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <input/input.h>
#include <notification/notification-messages.h>
#include <notification/notification_messages.h>
void vibro_test_draw_callback(Canvas* canvas, void* ctx) {
canvas_clear(canvas);

View File

@ -0,0 +1,434 @@
#include "animation_manager.h"
#include "furi_hal_delay.h"
#include "portmacro.h"
#include "views/bubble_animation_view.h"
#include "animation_storage.h"
#include <cmsis_os2.h>
#include <dolphin/dolphin.h>
#include <furi/check.h>
#include <furi/pubsub.h>
#include <furi/record.h>
#include <m-string.h>
#include <power/power_service/power.h>
#include <stdint.h>
#include <storage/storage.h>
#include <dolphin/dolphin_i.h>
#include <storage/filesystem_api_defines.h>
#define TAG "AnimationManager"
typedef enum {
AnimationManagerStateIdle,
AnimationManagerStateBlocked,
AnimationManagerStateFreezedIdle,
AnimationManagerStateFreezedBlocked,
} AnimationManagerState;
struct AnimationManager {
bool sd_show_url;
bool sd_shown_no_db;
bool sd_shown_sd_ok;
AnimationManagerState state;
FuriPubSubSubscription* pubsub_subscription_storage;
FuriPubSubSubscription* pubsub_subscription_dolphin;
BubbleAnimationView* animation_view;
osTimerId_t idle_animation_timer;
StorageAnimation* current_animation;
AnimationManagerInteractCallback interact_callback;
AnimationManagerSetNewIdleAnimationCallback new_idle_callback;
AnimationManagerSetNewIdleAnimationCallback check_blocking_callback;
void* context;
string_t freezed_animation_name;
int32_t freezed_animation_time_left;
};
static StorageAnimation*
animation_manager_select_idle_animation(AnimationManager* animation_manager);
static void animation_manager_replace_current_animation(
AnimationManager* animation_manager,
StorageAnimation* storage_animation);
static void animation_manager_start_new_idle(AnimationManager* animation_manager);
static bool animation_manager_check_blocking(AnimationManager* animation_manager);
void animation_manager_set_context(AnimationManager* animation_manager, void* context) {
furi_assert(animation_manager);
animation_manager->context = context;
}
void animation_manager_set_new_idle_callback(
AnimationManager* animation_manager,
AnimationManagerSetNewIdleAnimationCallback callback) {
furi_assert(animation_manager);
animation_manager->new_idle_callback = callback;
}
void animation_manager_set_check_callback(
AnimationManager* animation_manager,
AnimationManagerCheckBlockingCallback callback) {
furi_assert(animation_manager);
animation_manager->check_blocking_callback = callback;
}
void animation_manager_set_interact_callback(
AnimationManager* animation_manager,
AnimationManagerInteractCallback callback) {
furi_assert(animation_manager);
animation_manager->interact_callback = callback;
}
static void animation_manager_check_blocking_callback(const void* message, void* context) {
furi_assert(context);
AnimationManager* animation_manager = context;
if(animation_manager->check_blocking_callback) {
animation_manager->check_blocking_callback(animation_manager->context);
}
}
static void animation_manager_timer_callback(void* context) {
furi_assert(context);
AnimationManager* animation_manager = context;
if(animation_manager->new_idle_callback) {
animation_manager->new_idle_callback(animation_manager->context);
}
}
static void animation_manager_interact_callback(void* context) {
furi_assert(context);
AnimationManager* animation_manager = context;
if(animation_manager->interact_callback) {
animation_manager->interact_callback(animation_manager->context);
}
}
/* reaction to animation_manager->interact_callback() */
void animation_manager_check_blocking_process(AnimationManager* animation_manager) {
furi_assert(animation_manager);
if(animation_manager->state == AnimationManagerStateIdle) {
animation_manager_check_blocking(animation_manager);
}
}
/* reaction to animation_manager->new_idle_callback() */
void animation_manager_new_idle_process(AnimationManager* animation_manager) {
furi_assert(animation_manager);
if(animation_manager->state == AnimationManagerStateIdle) {
animation_manager_start_new_idle(animation_manager);
}
}
/* reaction to animation_manager->check_blocking_callback() */
void animation_manager_interact_process(AnimationManager* animation_manager) {
furi_assert(animation_manager);
if(animation_manager->state == AnimationManagerStateBlocked) {
/* check if new blocking animation has to be displayed */
bool blocked = animation_manager_check_blocking(animation_manager);
if(!blocked) {
animation_manager_start_new_idle(animation_manager);
}
}
}
static void animation_manager_start_new_idle(AnimationManager* animation_manager) {
furi_assert(animation_manager);
StorageAnimation* new_animation = animation_manager_select_idle_animation(animation_manager);
animation_manager_replace_current_animation(animation_manager, new_animation);
const BubbleAnimation* bubble_animation =
animation_storage_get_bubble_animation(animation_manager->current_animation);
animation_manager->state = AnimationManagerStateIdle;
osTimerStart(animation_manager->idle_animation_timer, bubble_animation->duration * 1000);
}
static bool animation_manager_check_blocking(AnimationManager* animation_manager) {
furi_assert(animation_manager);
StorageAnimation* blocking_animation = NULL;
Storage* storage = furi_record_open("storage");
FS_Error sd_status = storage_sd_status(storage);
if(sd_status == FSE_INTERNAL) {
blocking_animation = animation_storage_find_animation(BAD_SD_ANIMATION_NAME);
} else if(sd_status == FSE_NOT_READY) {
animation_manager->sd_shown_sd_ok = false;
animation_manager->sd_shown_no_db = false;
} else if(sd_status == FSE_OK) {
if(!animation_manager->sd_shown_sd_ok) {
blocking_animation = animation_storage_find_animation(SD_OK_ANIMATION_NAME);
animation_manager->sd_shown_sd_ok = true;
} else if(!animation_manager->sd_shown_no_db) {
bool db_exists = storage_common_stat(storage, "/ext/Manifest", NULL) == FSE_OK;
if(!db_exists) {
blocking_animation = animation_storage_find_animation(NO_DB_ANIMATION_NAME);
animation_manager->sd_shown_no_db = true;
animation_manager->sd_show_url = true;
}
} else if(animation_manager->sd_show_url) {
blocking_animation = animation_storage_find_animation(URL_ANIMATION_NAME);
animation_manager->sd_show_url = false;
}
}
Dolphin* dolphin = furi_record_open("dolphin");
DolphinStats stats = dolphin_stats(dolphin);
furi_record_close("dolphin");
if(!blocking_animation && stats.level_up_is_pending) {
blocking_animation = animation_storage_find_animation(LEVELUP_ANIMATION_NAME);
}
if(blocking_animation) {
osTimerStop(animation_manager->idle_animation_timer);
animation_manager_replace_current_animation(animation_manager, blocking_animation);
/* no starting timer because its blocking animation */
animation_manager->state = AnimationManagerStateBlocked;
}
furi_record_close("storage");
return !!blocking_animation;
}
static void animation_manager_replace_current_animation(
AnimationManager* animation_manager,
StorageAnimation* storage_animation) {
furi_assert(storage_animation);
StorageAnimation* previous_animation = animation_manager->current_animation;
const BubbleAnimation* animation = animation_storage_get_bubble_animation(storage_animation);
bubble_animation_view_set_animation(animation_manager->animation_view, animation);
const char* new_name = string_get_cstr(animation_storage_get_meta(storage_animation)->name);
FURI_LOG_I(TAG, "Select \'%s\' animation", new_name);
animation_manager->current_animation = storage_animation;
if(previous_animation) {
animation_storage_free_storage_animation(&previous_animation);
}
}
AnimationManager* animation_manager_alloc(void) {
animation_storage_initialize_internal_animations();
AnimationManager* animation_manager = furi_alloc(sizeof(AnimationManager));
animation_manager->animation_view = bubble_animation_view_alloc();
string_init(animation_manager->freezed_animation_name);
animation_manager->idle_animation_timer =
osTimerNew(animation_manager_timer_callback, osTimerOnce, animation_manager, NULL);
bubble_animation_view_set_interact_callback(
animation_manager->animation_view, animation_manager_interact_callback, animation_manager);
Storage* storage = furi_record_open("storage");
animation_manager->pubsub_subscription_storage = furi_pubsub_subscribe(
storage_get_pubsub(storage), animation_manager_check_blocking_callback, animation_manager);
furi_record_close("storage");
Dolphin* dolphin = furi_record_open("dolphin");
animation_manager->pubsub_subscription_dolphin = furi_pubsub_subscribe(
dolphin_get_pubsub(dolphin), animation_manager_check_blocking_callback, animation_manager);
furi_record_close("dolphin");
animation_manager->sd_shown_sd_ok = true;
if(!animation_manager_check_blocking(animation_manager)) {
animation_manager_start_new_idle(animation_manager);
}
return animation_manager;
}
void animation_manager_free(AnimationManager* animation_manager) {
furi_assert(animation_manager);
Dolphin* dolphin = furi_record_open("dolphin");
furi_pubsub_unsubscribe(
dolphin_get_pubsub(dolphin), animation_manager->pubsub_subscription_dolphin);
furi_record_close("dolphin");
Storage* storage = furi_record_open("storage");
furi_pubsub_unsubscribe(
storage_get_pubsub(storage), animation_manager->pubsub_subscription_storage);
furi_record_close("storage");
string_clear(animation_manager->freezed_animation_name);
bubble_animation_view_free(animation_manager->animation_view);
osTimerDelete(animation_manager->idle_animation_timer);
}
View* animation_manager_get_animation_view(AnimationManager* animation_manager) {
furi_assert(animation_manager);
return bubble_animation_get_view(animation_manager->animation_view);
}
static StorageAnimation*
animation_manager_select_idle_animation(AnimationManager* animation_manager) {
StorageAnimationList_t animation_list;
StorageAnimationList_init(animation_list);
animation_storage_fill_animation_list(&animation_list);
Power* power = furi_record_open("power");
bool battery_is_well = power_is_battery_healthy(power);
furi_record_close("power");
Storage* storage = furi_record_open("storage");
FS_Error sd_status = storage_sd_status(storage);
furi_record_close("storage");
Dolphin* dolphin = furi_record_open("dolphin");
DolphinStats stats = dolphin_stats(dolphin);
uint32_t whole_weight = 0;
StorageAnimationList_it_t it;
for(StorageAnimationList_it(it, animation_list); !StorageAnimationList_end_p(it);) {
StorageAnimation* storage_animation = *StorageAnimationList_ref(it);
const StorageAnimationMeta* meta = animation_storage_get_meta(storage_animation);
bool skip_animation = false;
if(battery_is_well && !string_cmp_str(meta->name, BAD_BATTERY_ANIMATION_NAME)) {
skip_animation = true;
} else if((sd_status != FSE_NOT_READY) && !string_cmp_str(meta->name, NO_SD_ANIMATION_NAME)) {
skip_animation = true;
} else if((stats.butthurt < meta->min_butthurt) || (stats.butthurt > meta->max_butthurt)) {
skip_animation = true;
} else if((stats.level < meta->min_level) || (stats.level > meta->max_level)) {
skip_animation = true;
}
if(skip_animation) {
animation_storage_free_storage_animation(&storage_animation);
/* remove and increase iterator */
StorageAnimationList_remove(animation_list, it);
} else {
whole_weight += meta->weight;
StorageAnimationList_next(it);
}
}
uint32_t lucky_number = random() % whole_weight;
uint32_t weight = 0;
StorageAnimation* selected = NULL;
for
M_EACH(item, animation_list, StorageAnimationList_t) {
if(lucky_number < weight) {
break;
}
weight += animation_storage_get_meta(*item)->weight;
selected = *item;
}
for
M_EACH(item, animation_list, StorageAnimationList_t) {
if(*item != selected) {
animation_storage_free_storage_animation(item);
}
}
StorageAnimationList_clear(animation_list);
furi_record_close("dolphin");
/* cache animation, if failed - choose reliable animation */
if(!animation_storage_get_bubble_animation(selected)) {
const char* name = string_get_cstr(animation_storage_get_meta(selected)->name);
FURI_LOG_E(TAG, "Can't upload animation described in manifest: \'%s\'", name);
animation_storage_free_storage_animation(&selected);
selected = animation_storage_find_animation(HARDCODED_ANIMATION_NAME);
}
furi_assert(selected);
return selected;
}
void animation_manager_unload_and_stall_animation(AnimationManager* animation_manager) {
furi_assert(animation_manager);
furi_assert(animation_manager->current_animation);
furi_assert(!string_size(animation_manager->freezed_animation_name));
furi_assert(
(animation_manager->state == AnimationManagerStateIdle) ||
(animation_manager->state == AnimationManagerStateBlocked));
if(animation_manager->state == AnimationManagerStateBlocked) {
animation_manager->state = AnimationManagerStateFreezedBlocked;
} else if(animation_manager->state == AnimationManagerStateIdle) {
animation_manager->state = AnimationManagerStateFreezedIdle;
animation_manager->freezed_animation_time_left =
xTimerGetExpiryTime(animation_manager->idle_animation_timer) - xTaskGetTickCount();
if(animation_manager->freezed_animation_time_left < 0) {
animation_manager->freezed_animation_time_left = 0;
}
osTimerStop(animation_manager->idle_animation_timer);
} else {
furi_assert(0);
}
StorageAnimationMeta* meta = animation_storage_get_meta(animation_manager->current_animation);
/* copy str, not move, because it can be internal animation */
string_set(animation_manager->freezed_animation_name, meta->name);
bubble_animation_freeze(animation_manager->animation_view);
animation_storage_free_storage_animation(&animation_manager->current_animation);
}
void animation_manager_load_and_continue_animation(AnimationManager* animation_manager) {
furi_assert(animation_manager);
furi_assert(!animation_manager->current_animation);
furi_assert(string_size(animation_manager->freezed_animation_name));
furi_assert(
(animation_manager->state == AnimationManagerStateFreezedIdle) ||
(animation_manager->state == AnimationManagerStateFreezedBlocked));
if(animation_manager->state == AnimationManagerStateFreezedBlocked) {
StorageAnimation* restore_animation = animation_storage_find_animation(
string_get_cstr(animation_manager->freezed_animation_name));
/* all blocked animations must be in flipper -> we can
* always find blocking animation */
furi_assert(restore_animation);
animation_manager_replace_current_animation(animation_manager, restore_animation);
animation_manager->state = AnimationManagerStateBlocked;
} else if(animation_manager->state == AnimationManagerStateFreezedIdle) {
/* check if we missed some system notifications, and set current_animation */
bool blocked = animation_manager_check_blocking(animation_manager);
if(!blocked) {
/* if no blocking - try restore last one idle */
StorageAnimation* restore_animation = animation_storage_find_animation(
string_get_cstr(animation_manager->freezed_animation_name));
if(restore_animation) {
animation_manager_replace_current_animation(animation_manager, restore_animation);
animation_manager->state = AnimationManagerStateIdle;
if(animation_manager->freezed_animation_time_left) {
osTimerStart(
animation_manager->idle_animation_timer,
animation_manager->freezed_animation_time_left);
} else {
const BubbleAnimation* animation = animation_storage_get_bubble_animation(
animation_manager->current_animation);
osTimerStart(
animation_manager->idle_animation_timer, animation->duration * 1000);
}
} else {
FURI_LOG_E(
TAG,
"Failed to restore \'%s\'",
string_get_cstr(animation_manager->freezed_animation_name));
}
}
} else {
/* Unknown state is an error. But not in release version.*/
furi_assert(0);
}
/* if can't restore previous animation - select new */
if(!animation_manager->current_animation) {
animation_manager_start_new_idle(animation_manager);
}
FURI_LOG_D(
TAG,
"Load & Continue with \'%s\'",
string_get_cstr(animation_storage_get_meta(animation_manager->current_animation)->name));
bubble_animation_unfreeze(animation_manager->animation_view);
string_reset(animation_manager->freezed_animation_name);
furi_assert(animation_manager->current_animation);
}

View File

@ -0,0 +1,151 @@
#pragma once
#include "dolphin/dolphin.h"
#include <gui/view.h>
#include <stdint.h>
typedef struct AnimationManager AnimationManager;
typedef struct {
uint8_t x;
uint8_t y;
const char* str;
Align horizontal;
Align vertical;
} Bubble;
typedef struct FrameBubble {
Bubble bubble;
uint8_t starts_at_frame;
uint8_t ends_at_frame;
struct FrameBubble* next_bubble;
} FrameBubble;
typedef struct {
FrameBubble** frame_bubbles;
uint8_t frame_bubbles_count;
const Icon** icons;
uint8_t passive_frames;
uint8_t active_frames;
uint8_t active_cycles;
uint8_t frame_rate;
uint16_t duration;
uint16_t active_cooldown;
} BubbleAnimation;
typedef void (*AnimationManagerSetNewIdleAnimationCallback)(void* context);
typedef void (*AnimationManagerCheckBlockingCallback)(void* context);
typedef void (*AnimationManagerInteractCallback)(void*);
/**
* Allocate Animation Manager
*
* @return animation manager instance
*/
AnimationManager* animation_manager_alloc(void);
/**
* Free Animation Manager
*
* @animation_manager instance
*/
void animation_manager_free(AnimationManager* animation_manager);
/**
* Get View of Animation Manager
*
* @animation_manager instance
* @return view
*/
View* animation_manager_get_animation_view(AnimationManager* animation_manager);
/**
* Set context for all callbacks for Animation Manager
*
* @animation_manager instance
* @context context
*/
void animation_manager_set_context(AnimationManager* animation_manager, void* context);
/**
* Set callback for Animation Manager for defered calls
* for animation_manager_new_idle_process().
* Animation Manager doesn't have it's own thread, so main thread gives
* callbacks to A.M. to call when it should perform some inner manipulations.
* This callback is called from other threads and should notify main thread
* when to call animation_manager_new_idle_process().
* So scheme is this:
* A.M. sets callbacks,
* callbacks notifies main thread
* main thread in its own context calls appropriate *_process() function.
*
* @animation_manager instance
* @callback callback
*/
void animation_manager_set_new_idle_callback(
AnimationManager* animation_manager,
AnimationManagerSetNewIdleAnimationCallback callback);
/**
* Function to call in main thread as a response to
* set_new_idle_callback's call.
*
* @animation_manager instance
*/
void animation_manager_new_idle_process(AnimationManager* animation_manager);
/**
* Set callback for Animation Manager for defered calls
* for animation_manager_check_blocking_process().
*
* @animation_manager instance
* @callback callback
*/
void animation_manager_set_check_callback(
AnimationManager* animation_manager,
AnimationManagerCheckBlockingCallback callback);
/**
* Function to call in main thread as a response to
* set_new_idle_callback's call.
*
* @animation_manager instance
*/
void animation_manager_check_blocking_process(AnimationManager* animation_manager);
/**
* Set callback for Animation Manager for defered calls
* for animation_manager_interact_process().
*
* @animation_manager instance
* @callback callback
*/
void animation_manager_set_interact_callback(
AnimationManager* animation_manager,
AnimationManagerInteractCallback callback);
/**
* Function to call in main thread as a response to
* set_new_idle_callback's call.
*
* @animation_manager instance
*/
void animation_manager_interact_process(AnimationManager* animation_manager);
/**
* Unload and Stall animation actions. Draw callback in view
* paints first frame of current animation until
* animation_manager_load_and_continue_animation() is called.
* Can't be called multiple times. Every Stall has to be finished
* with Continue.
*
* @animation_manager instance
*/
void animation_manager_unload_and_stall_animation(AnimationManager* animation_manager);
/**
* Load and Contunue execution of animation manager.
*
* @animation_manager instance
*/
void animation_manager_load_and_continue_animation(AnimationManager* animation_manager);

View File

@ -0,0 +1,485 @@
#include "animation_manager.h"
#include "file_worker.h"
#include "flipper_file.h"
#include "furi/common_defines.h"
#include "furi/memmgr.h"
#include "furi/record.h"
#include "animation_storage.h"
#include "gui/canvas.h"
#include "m-string.h"
#include "pb.h"
#include "pb_decode.h"
#include "storage/filesystem_api_defines.h"
#include "storage/storage.h"
#include "animation_storage_i.h"
#include <stdint.h>
#include <gui/icon_i.h>
// Read documentation before using it
#include <furi/dangerous_defines.h>
#define ANIMATION_META_FILE "meta.txt"
#define ANIMATION_DIR "/ext/dolphin/animations"
#define ANIMATION_MANIFEST_FILE ANIMATION_DIR "/manifest.txt"
#define TAG "AnimationStorage"
#define DEBUG_PB 0
static void animation_storage_free_bubbles(BubbleAnimation* animation);
static void animation_storage_free_frames(BubbleAnimation* animation);
static void animation_storage_free_animation(BubbleAnimation** storage_animation);
static BubbleAnimation* animation_storage_load_animation(const char* name);
void animation_storage_fill_animation_list(StorageAnimationList_t* animation_list) {
furi_assert(sizeof(StorageAnimationList_t) == sizeof(void*));
furi_assert(!StorageAnimationList_size(*animation_list));
Storage* storage = furi_record_open("storage");
FlipperFile* file = flipper_file_alloc(storage);
/* Forbid skipping fields */
flipper_file_set_strict_mode(file, true);
string_t header;
string_init(header);
do {
uint32_t u32value;
StorageAnimation* storage_animation = NULL;
if(FSE_OK != storage_sd_status(storage)) break;
if(!flipper_file_open_existing(file, ANIMATION_MANIFEST_FILE)) break;
if(!flipper_file_read_header(file, header, &u32value)) break;
if(string_cmp_str(header, "Flipper Animation Manifest")) break;
do {
storage_animation = furi_alloc(sizeof(StorageAnimation));
storage_animation->external = true;
storage_animation->animation = NULL;
if(!flipper_file_read_string(file, "Name", storage_animation->meta.name)) break;
if(!flipper_file_read_uint32(file, "Min butthurt", &u32value, 1)) break;
storage_animation->meta.min_butthurt = u32value;
if(!flipper_file_read_uint32(file, "Max butthurt", &u32value, 1)) break;
storage_animation->meta.max_butthurt = u32value;
if(!flipper_file_read_uint32(file, "Min level", &u32value, 1)) break;
storage_animation->meta.min_level = u32value;
if(!flipper_file_read_uint32(file, "Max level", &u32value, 1)) break;
storage_animation->meta.max_level = u32value;
if(!flipper_file_read_uint32(file, "Weight", &u32value, 1)) break;
storage_animation->meta.weight = u32value;
StorageAnimationList_push_back(*animation_list, storage_animation);
} while(1);
animation_storage_free_storage_animation(&storage_animation);
} while(0);
string_clear(header);
flipper_file_close(file);
flipper_file_free(file);
// add hard-coded animations
for(int i = 0; i < COUNT_OF(StorageAnimationInternal); ++i) {
StorageAnimationList_push_back(*animation_list, &StorageAnimationInternal[i]);
}
furi_record_close("storage");
}
StorageAnimation* animation_storage_find_animation(const char* name) {
furi_assert(name);
furi_assert(strlen(name));
StorageAnimation* storage_animation = NULL;
/* look through internal animations */
for(int i = 0; i < COUNT_OF(StorageAnimationInternal); ++i) {
if(!string_cmp_str(StorageAnimationInternal[i].meta.name, name)) {
storage_animation = &StorageAnimationInternal[i];
break;
}
}
/* look through external animations */
if(!storage_animation) {
BubbleAnimation* animation = animation_storage_load_animation(name);
if(animation != NULL) {
storage_animation = furi_alloc(sizeof(StorageAnimation));
storage_animation->animation = animation;
storage_animation->external = true;
/* meta data takes part in random animation selection, so it
* doesn't need here as we exactly know which animation we need,
* that's why we can ignore reading manifest.txt file
* filling meta data by zeroes */
storage_animation->meta.min_butthurt = 0;
storage_animation->meta.max_butthurt = 0;
storage_animation->meta.min_level = 0;
storage_animation->meta.max_level = 0;
storage_animation->meta.weight = 0;
string_init_set_str(storage_animation->meta.name, name);
}
}
return storage_animation;
}
StorageAnimationMeta* animation_storage_get_meta(StorageAnimation* storage_animation) {
furi_assert(storage_animation);
return &storage_animation->meta;
}
const BubbleAnimation*
animation_storage_get_bubble_animation(StorageAnimation* storage_animation) {
furi_assert(storage_animation);
animation_storage_cache_animation(storage_animation);
return storage_animation->animation;
}
void animation_storage_cache_animation(StorageAnimation* storage_animation) {
furi_assert(storage_animation);
if(storage_animation->external) {
if(!storage_animation->animation) {
storage_animation->animation =
animation_storage_load_animation(string_get_cstr(storage_animation->meta.name));
}
}
}
static void animation_storage_free_animation(BubbleAnimation** animation) {
furi_assert(animation);
if(*animation) {
animation_storage_free_bubbles(*animation);
animation_storage_free_frames(*animation);
free(*animation);
*animation = NULL;
}
}
void animation_storage_free_storage_animation(StorageAnimation** storage_animation) {
furi_assert(storage_animation);
furi_assert(*storage_animation);
if((*storage_animation)->external) {
animation_storage_free_animation((BubbleAnimation**)&(*storage_animation)->animation);
string_clear((*storage_animation)->meta.name);
free(*storage_animation);
}
*storage_animation = NULL;
}
static bool animation_storage_cast_align(string_t align_str, Align* align) {
if(!string_cmp_str(align_str, "Bottom")) {
*align = AlignBottom;
} else if(!string_cmp_str(align_str, "Top")) {
*align = AlignTop;
} else if(!string_cmp_str(align_str, "Left")) {
*align = AlignLeft;
} else if(!string_cmp_str(align_str, "Right")) {
*align = AlignRight;
} else if(!string_cmp_str(align_str, "Center")) {
*align = AlignCenter;
} else {
return false;
}
return true;
}
static void animation_storage_free_frames(BubbleAnimation* animation) {
furi_assert(animation);
furi_assert(animation->icons);
const Icon** icons = animation->icons;
uint16_t frames = animation->active_frames + animation->passive_frames;
furi_assert(frames > 0);
for(int i = 0; i < frames; ++i) {
if(!icons[i]) continue;
const Icon* icon = icons[i];
free((void*)icon->frames[0]);
free(icon->frames);
free((void*)icon);
for(int j = i; j < frames; ++j) {
if(icons[j] == icon) {
icons[j] = NULL;
}
}
}
free(animation->icons);
animation->icons = NULL;
}
static Icon* animation_storage_alloc_icon(size_t frame_size) {
Icon* icon = furi_alloc(sizeof(Icon));
icon->frames = furi_alloc(sizeof(const uint8_t*));
icon->frames[0] = furi_alloc(frame_size);
return icon;
}
static void animation_storage_free_icon(Icon* icon) {
free((void*)icon->frames[0]);
free(icon->frames);
free(icon);
}
static bool animation_storage_load_frames(
Storage* storage,
const char* name,
BubbleAnimation* animation,
uint32_t* frame_order,
uint32_t width,
uint32_t height) {
furi_assert(!animation->icons);
uint16_t frame_order_size = animation->passive_frames + animation->active_frames;
bool frames_ok = false;
animation->icons = furi_alloc(sizeof(const Icon*) * frame_order_size);
File* file = storage_file_alloc(storage);
FileInfo file_info;
string_t filename;
string_init(filename);
size_t max_filesize = ROUND_UP_TO(width, 8) * height + 1;
for(int i = 0; i < frame_order_size; ++i) {
if(animation->icons[i]) continue;
frames_ok = false;
string_printf(filename, ANIMATION_DIR "/%s/frame_%d.bm", name, frame_order[i]);
if(storage_common_stat(storage, string_get_cstr(filename), &file_info) != FSE_OK) break;
if(file_info.size > max_filesize) {
FURI_LOG_E(
TAG,
"Filesize %d, max: %d (width %d, height %d)",
file_info.size,
max_filesize,
width,
height);
break;
}
if(!storage_file_open(file, string_get_cstr(filename), FSAM_READ, FSOM_OPEN_EXISTING)) {
FURI_LOG_E(TAG, "Can't open file \'%s\'", string_get_cstr(filename));
break;
}
Icon* icon = animation_storage_alloc_icon(file_info.size);
if(storage_file_read(file, (void*)icon->frames[0], file_info.size) != file_info.size) {
FURI_LOG_E(TAG, "Read failed: \'%s\'", string_get_cstr(filename));
animation_storage_free_icon(icon);
break;
}
storage_file_close(file);
FURI_CONST_ASSIGN(icon->frame_count, 1);
FURI_CONST_ASSIGN(icon->frame_rate, 0);
FURI_CONST_ASSIGN(icon->height, height);
FURI_CONST_ASSIGN(icon->width, width);
/* Claim 1 allocation for 1 files blob and several links to it */
for(int j = i; j < frame_order_size; ++j) {
if(frame_order[i] == frame_order[j]) {
animation->icons[j] = icon;
}
}
frames_ok = true;
}
if(!frames_ok) {
FURI_LOG_E(
TAG,
"Load \'%s\' failed, %dx%d, size: %d",
string_get_cstr(filename),
width,
height,
file_info.size);
animation_storage_free_frames(animation);
animation->icons = NULL;
} else {
for(int i = 0; i < frame_order_size; ++i) {
furi_check(animation->icons[i]);
furi_check(animation->icons[i]->frames[0]);
}
}
storage_file_free(file);
string_clear(filename);
return frames_ok;
}
static bool animation_storage_load_bubbles(BubbleAnimation* animation, FlipperFile* ff) {
uint32_t u32value;
string_t str;
string_init(str);
bool success = false;
furi_assert(!animation->frame_bubbles);
do {
if(!flipper_file_read_uint32(ff, "Bubble slots", &u32value, 1)) break;
if(u32value > 20) break;
animation->frame_bubbles_count = u32value;
if(animation->frame_bubbles_count == 0) {
animation->frame_bubbles = NULL;
success = true;
break;
}
animation->frame_bubbles =
furi_alloc(sizeof(FrameBubble*) * animation->frame_bubbles_count);
uint32_t current_slot = 0;
for(int i = 0; i < animation->frame_bubbles_count; ++i) {
animation->frame_bubbles[i] = furi_alloc(sizeof(FrameBubble));
}
FrameBubble* bubble = animation->frame_bubbles[0];
int8_t index = -1;
for(;;) {
if(!flipper_file_read_uint32(ff, "Slot", &current_slot, 1)) break;
if((current_slot != 0) && (index == -1)) break;
if(current_slot == index) {
bubble->next_bubble = furi_alloc(sizeof(FrameBubble));
bubble = bubble->next_bubble;
} else if(current_slot == index + 1) {
++index;
bubble = animation->frame_bubbles[index];
} else {
/* slots have to start from 0, be ascending sorted, and
* have exact number of slots as specified in "Bubble slots" */
break;
}
if(index >= animation->frame_bubbles_count) break;
if(!flipper_file_read_uint32(ff, "X", &u32value, 1)) break;
bubble->bubble.x = u32value;
if(!flipper_file_read_uint32(ff, "Y", &u32value, 1)) break;
bubble->bubble.y = u32value;
if(!flipper_file_read_string(ff, "Text", str)) break;
if(string_size(str) > 100) break;
string_replace_all_str(str, "\\n", "\n");
bubble->bubble.str = furi_alloc(string_size(str) + 1);
strcpy((char*)bubble->bubble.str, string_get_cstr(str));
if(!flipper_file_read_string(ff, "AlignH", str)) break;
if(!animation_storage_cast_align(str, &bubble->bubble.horizontal)) break;
if(!flipper_file_read_string(ff, "AlignV", str)) break;
if(!animation_storage_cast_align(str, &bubble->bubble.vertical)) break;
if(!flipper_file_read_uint32(ff, "StartFrame", &u32value, 1)) break;
bubble->starts_at_frame = u32value;
if(!flipper_file_read_uint32(ff, "EndFrame", &u32value, 1)) break;
bubble->ends_at_frame = u32value;
}
success = (index + 1) == animation->frame_bubbles_count;
} while(0);
if(!success) {
if(animation->frame_bubbles) {
FURI_LOG_E(TAG, "Failed to load animation bubbles");
animation_storage_free_bubbles(animation);
}
}
string_clear(str);
return success;
}
static BubbleAnimation* animation_storage_load_animation(const char* name) {
furi_assert(name);
BubbleAnimation* animation = furi_alloc(sizeof(BubbleAnimation));
uint32_t height = 0;
uint32_t width = 0;
uint32_t* u32array = NULL;
Storage* storage = furi_record_open("storage");
FlipperFile* ff = flipper_file_alloc(storage);
/* Forbid skipping fields */
flipper_file_set_strict_mode(ff, true);
string_t str;
string_init(str);
animation->frame_bubbles = NULL;
bool success = false;
do {
uint32_t u32value;
if(FSE_OK != storage_sd_status(storage)) break;
string_printf(str, ANIMATION_DIR "/%s/" ANIMATION_META_FILE, name);
if(!flipper_file_open_existing(ff, string_get_cstr(str))) break;
if(!flipper_file_read_header(ff, str, &u32value)) break;
if(string_cmp_str(str, "Flipper Animation")) break;
if(!flipper_file_read_uint32(ff, "Width", &width, 1)) break;
if(!flipper_file_read_uint32(ff, "Height", &height, 1)) break;
if(!flipper_file_read_uint32(ff, "Passive frames", &u32value, 1)) break;
animation->passive_frames = u32value;
if(!flipper_file_read_uint32(ff, "Active frames", &u32value, 1)) break;
animation->active_frames = u32value;
uint8_t frames = animation->passive_frames + animation->active_frames;
u32array = furi_alloc(sizeof(uint32_t) * frames);
if(!flipper_file_read_uint32(ff, "Frames order", u32array, frames)) break;
/* passive and active frames must be loaded up to this point */
if(!animation_storage_load_frames(storage, name, animation, u32array, width, height))
break;
if(!flipper_file_read_uint32(ff, "Active cycles", &u32value, 1)) break;
animation->active_cycles = u32value;
if(!flipper_file_read_uint32(ff, "Frame rate", &u32value, 1)) break;
animation->frame_rate = u32value;
if(!flipper_file_read_uint32(ff, "Duration", &u32value, 1)) break;
animation->duration = u32value;
if(!flipper_file_read_uint32(ff, "Active cooldown", &u32value, 1)) break;
animation->active_cooldown = u32value;
if(!animation_storage_load_bubbles(animation, ff)) break;
success = true;
} while(0);
string_clear(str);
flipper_file_close(ff);
flipper_file_free(ff);
if(u32array) {
free(u32array);
}
if(!success) {
free(animation);
animation = NULL;
}
return animation;
}
static void animation_storage_free_bubbles(BubbleAnimation* animation) {
if(!animation->frame_bubbles) return;
for(int i = 0; i < animation->frame_bubbles_count;) {
FrameBubble** bubble = &animation->frame_bubbles[i];
if((*bubble) == NULL) break;
while((*bubble)->next_bubble != NULL) {
bubble = &(*bubble)->next_bubble;
}
if((*bubble)->bubble.str) {
free((void*)(*bubble)->bubble.str);
}
if((*bubble) == animation->frame_bubbles[i]) {
++i;
}
free(*bubble);
*bubble = NULL;
}
free(animation->frame_bubbles);
animation->frame_bubbles = NULL;
}

View File

@ -0,0 +1,98 @@
#pragma once
#include <stdint.h>
#include <m-list.h>
#include "views/bubble_animation_view.h"
#include <m-string.h>
#define HARDCODED_ANIMATION_NAME "tv"
#define NO_SD_ANIMATION_NAME "no_sd"
#define BAD_BATTERY_ANIMATION_NAME "bad_battery"
#define NO_DB_ANIMATION_NAME "no_db"
#define BAD_SD_ANIMATION_NAME "bad_sd"
#define SD_OK_ANIMATION_NAME "sd_ok"
#define URL_ANIMATION_NAME "url"
#define LEVELUP_ANIMATION_NAME "level"
/** Main structure to handle animation data.
* Contains all, including animation playing data (BubbleAnimation),
* data for random animation selection (StorageAnimationMeta) and
* flag of location internal/external */
typedef struct StorageAnimation StorageAnimation;
typedef struct {
string_t name;
uint8_t min_butthurt;
uint8_t max_butthurt;
uint8_t min_level;
uint8_t max_level;
uint8_t weight;
} StorageAnimationMeta;
/** Container to return available animations list */
LIST_DEF(StorageAnimationList, StorageAnimation*, M_PTR_OPLIST)
#define M_OPL_StorageAnimationList_t() LIST_OPLIST(StorageAnimationList)
/**
* Fill list of available animations.
* List will contain all idle animations on inner flash
* and all available on SD-card, mentioned in manifest.txt.
* Performs caching of animation. If fail - falls back to
* inner animation.
* List has to be initialized.
*
* @list list to fill with animations data
*/
void animation_storage_fill_animation_list(StorageAnimationList_t* list);
/**
* Get bubble animation of storage animation.
* Bubble Animation is a structure which describes animation
* independent of it's place of storage and meta data.
* It contain all what is need to be played.
* If storage_animation is not cached - caches it.
*
* @storage_animation animation from which extract bubble animation
* @return bubble_animation, NULL if failed to cache data.
*/
const BubbleAnimation* animation_storage_get_bubble_animation(StorageAnimation* storage_animation);
/**
* Performs caching animation data (Bubble Animation)
* if this is not done yet.
*
* @storage_animation animation to cache
*/
void animation_storage_cache_animation(StorageAnimation* storage_animation);
/**
* Find animation by name.
* Search through the inner flash, and SD-card if has.
*
* @name name of animation
* @return found animation. NULL if nothing found.
*/
StorageAnimation* animation_storage_find_animation(const char* name);
/**
* Get meta information of storage animation.
* This information allows to randomly select animation.
* Also it contains name. Never returns NULL.
*
* @storage_animation item of whom we have to extract meta.
* @return meta itself
*/
StorageAnimationMeta* animation_storage_get_meta(StorageAnimation* storage_animation);
/**
* Free storage_animation, which previously acquired
* by Animation Storage.
*
* @storage_animation item to free. NULL-ed after all.
*/
void animation_storage_free_storage_animation(StorageAnimation** storage_animation);
/**
* Has to be called at least 1 time to initialize runtime structures
* of animations in inner flash.
*/
void animation_storage_initialize_internal_animations(void);

View File

@ -0,0 +1,195 @@
#pragma once
#include "animation_storage.h"
#include "assets_icons.h"
#include "animation_manager.h"
#include "gui/canvas.h"
struct StorageAnimation {
const BubbleAnimation* animation;
bool external;
StorageAnimationMeta meta;
};
// Hard-coded, always available idle animation
FrameBubble tv_bubble1 = {
.bubble =
{.x = 1,
.y = 23,
.str = "Take the red pill",
.horizontal = AlignRight,
.vertical = AlignBottom},
.starts_at_frame = 7,
.ends_at_frame = 9,
.next_bubble = NULL,
};
FrameBubble tv_bubble2 = {
.bubble =
{.x = 1,
.y = 23,
.str = "I can joke better",
.horizontal = AlignRight,
.vertical = AlignBottom},
.starts_at_frame = 7,
.ends_at_frame = 9,
.next_bubble = NULL,
};
FrameBubble* tv_bubbles[] = {&tv_bubble1, &tv_bubble2};
const Icon* tv_icons[] = {
&I_tv1,
&I_tv2,
&I_tv3,
&I_tv4,
&I_tv5,
&I_tv6,
&I_tv7,
&I_tv8,
};
const BubbleAnimation tv_bubble_animation = {
.icons = tv_icons,
.frame_bubbles = tv_bubbles,
.frame_bubbles_count = COUNT_OF(tv_bubbles),
.passive_frames = 6,
.active_frames = 2,
.active_cycles = 2,
.frame_rate = 2,
.duration = 3600,
.active_cooldown = 5,
};
// System animation - no SD card
const Icon* no_sd_icons[] = {
&I_no_sd1,
&I_no_sd2,
&I_no_sd1,
&I_no_sd2,
&I_no_sd1,
&I_no_sd3,
&I_no_sd4,
&I_no_sd5,
&I_no_sd4,
&I_no_sd6,
};
FrameBubble no_sd_bubble = {
.bubble =
{.x = 40,
.y = 18,
.str = "Need an\nSD card",
.horizontal = AlignRight,
.vertical = AlignBottom},
.starts_at_frame = 0,
.ends_at_frame = 9,
.next_bubble = NULL,
};
FrameBubble* no_sd_bubbles[] = {&no_sd_bubble};
const BubbleAnimation no_sd_bubble_animation = {
.icons = no_sd_icons,
.frame_bubbles = no_sd_bubbles,
.frame_bubbles_count = COUNT_OF(no_sd_bubbles),
.passive_frames = 10,
.active_frames = 0,
.frame_rate = 2,
.duration = 3600,
.active_cooldown = 0,
.active_cycles = 0,
};
// BLOCKING ANIMATION - no_db, bad_sd, sd_ok, url
const Icon* no_db_icons[] = {
&I_no_databases1,
&I_no_databases2,
&I_no_databases3,
&I_no_databases4,
};
const BubbleAnimation no_db_bubble_animation = {
.icons = no_db_icons,
.passive_frames = COUNT_OF(no_db_icons),
.frame_rate = 2,
};
const Icon* bad_sd_icons[] = {
&I_card_bad1,
&I_card_bad2,
};
const BubbleAnimation bad_sd_bubble_animation = {
.icons = bad_sd_icons,
.passive_frames = COUNT_OF(bad_sd_icons),
.frame_rate = 2,
};
const Icon* url_icons[] = {
&I_url1,
&I_url2,
&I_url3,
&I_url4,
};
const BubbleAnimation url_bubble_animation = {
.icons = url_icons,
.passive_frames = COUNT_OF(url_icons),
.frame_rate = 2,
};
const Icon* sd_ok_icons[] = {
&I_card_ok1,
&I_card_ok2,
&I_card_ok3,
&I_card_ok4,
};
const BubbleAnimation sd_ok_bubble_animation = {
.icons = sd_ok_icons,
.passive_frames = COUNT_OF(sd_ok_icons),
.frame_rate = 2,
};
static StorageAnimation StorageAnimationInternal[] = {
{.animation = &tv_bubble_animation,
.external = false,
.meta =
{
.min_butthurt = 0,
.max_butthurt = 11,
.min_level = 1,
.max_level = 3,
.weight = 3,
}},
{.animation = &no_sd_bubble_animation,
.external = false,
.meta =
{
.min_butthurt = 0,
.max_butthurt = 14,
.min_level = 1,
.max_level = 3,
.weight = 6,
}},
{
.animation = &no_db_bubble_animation,
.external = false,
},
{
.animation = &bad_sd_bubble_animation,
.external = false,
},
{
.animation = &sd_ok_bubble_animation,
.external = false,
},
{
.animation = &url_bubble_animation,
.external = false,
},
};
void animation_storage_initialize_internal_animations(void) {
/* not in constructor - no memory pool yet */
/* called in 1 thread - no need in double check */
static bool initialized = false;
if(!initialized) {
initialized = true;
string_init_set_str(StorageAnimationInternal[0].meta.name, HARDCODED_ANIMATION_NAME);
string_init_set_str(StorageAnimationInternal[1].meta.name, NO_SD_ANIMATION_NAME);
string_init_set_str(StorageAnimationInternal[2].meta.name, NO_DB_ANIMATION_NAME);
string_init_set_str(StorageAnimationInternal[3].meta.name, BAD_SD_ANIMATION_NAME);
string_init_set_str(StorageAnimationInternal[4].meta.name, SD_OK_ANIMATION_NAME);
string_init_set_str(StorageAnimationInternal[5].meta.name, URL_ANIMATION_NAME);
}
}

View File

@ -0,0 +1,410 @@
#include "cmsis_os2.h"
#include "../animation_manager.h"
#include "../animation_storage.h"
#include "furi_hal_delay.h"
#include "furi_hal_resources.h"
#include "furi/check.h"
#include "furi/memmgr.h"
#include "gui/canvas.h"
#include "gui/elements.h"
#include "gui/view.h"
#include "input/input.h"
#include <furi.h>
#include "portmacro.h"
#include <gui/icon.h>
#include <stdint.h>
#include <FreeRTOS.h>
#include <timers.h>
#include "bubble_animation_view.h"
#include <gui/icon_i.h>
#define ACTIVE_SHIFT 2
typedef struct {
const BubbleAnimation* current;
const FrameBubble* current_bubble;
uint8_t current_frame;
uint8_t active_cycle;
uint8_t active_bubbles;
uint8_t passive_bubbles;
uint8_t active_shift;
TickType_t active_ended_at;
Icon* freeze_frame;
} BubbleAnimationViewModel;
struct BubbleAnimationView {
View* view;
osTimerId_t timer;
BubbleAnimationInteractCallback interact_callback;
void* interact_callback_context;
};
static void bubble_animation_activate(BubbleAnimationView* view, bool force);
static void bubble_animation_activate_right_now(BubbleAnimationView* view);
static uint8_t bubble_animation_get_icon_index(BubbleAnimationViewModel* model) {
furi_assert(model);
uint8_t icon_index = 0;
const BubbleAnimation* animation = model->current;
if(model->current_frame < animation->passive_frames) {
icon_index = model->current_frame;
} else {
icon_index =
(model->current_frame - animation->passive_frames) % animation->active_frames +
animation->passive_frames;
}
furi_assert(icon_index < (animation->passive_frames + animation->active_frames));
return icon_index;
}
static void bubble_animation_draw_callback(Canvas* canvas, void* model_) {
furi_assert(model_);
furi_assert(canvas);
BubbleAnimationViewModel* model = model_;
const BubbleAnimation* animation = model->current;
if(model->freeze_frame) {
uint8_t y_offset = canvas_height(canvas) - icon_get_height(model->freeze_frame);
canvas_draw_icon(canvas, 0, y_offset, model->freeze_frame);
return;
}
if(!animation) {
return;
}
furi_assert(model->current_frame < 255);
const Icon* icon = animation->icons[bubble_animation_get_icon_index(model)];
furi_assert(icon);
uint8_t y_offset = canvas_height(canvas) - icon_get_height(icon);
canvas_draw_icon(canvas, 0, y_offset, icon);
const FrameBubble* bubble = model->current_bubble;
if(bubble) {
if((model->current_frame >= bubble->starts_at_frame) &&
(model->current_frame <= bubble->ends_at_frame)) {
const Bubble* b = &bubble->bubble;
elements_bubble_str(canvas, b->x, b->y, b->str, b->horizontal, b->vertical);
}
}
}
static FrameBubble* bubble_animation_pick_bubble(BubbleAnimationViewModel* model, bool active) {
FrameBubble* bubble = NULL;
if((model->active_bubbles == 0) && (model->passive_bubbles == 0)) {
return NULL;
}
uint8_t index = random() % (active ? model->active_bubbles : model->passive_bubbles);
const BubbleAnimation* animation = model->current;
for(int i = 0; i < animation->frame_bubbles_count; ++i) {
if((animation->frame_bubbles[i]->starts_at_frame < animation->passive_frames) ^ active) {
if(!index) {
bubble = animation->frame_bubbles[i];
}
--index;
}
}
return bubble;
}
static bool bubble_animation_input_callback(InputEvent* event, void* context) {
furi_assert(context);
furi_assert(event);
BubbleAnimationView* animation_view = context;
bool consumed = false;
if(event->type == InputTypePress) {
bubble_animation_activate(animation_view, false);
}
if(event->key == InputKeyRight) {
/* Right button reserved for animation activation, so consume */
consumed = true;
if(event->type == InputTypeShort) {
if(animation_view->interact_callback) {
animation_view->interact_callback(animation_view->interact_callback_context);
}
}
} else if(event->key == InputKeyBack) {
/* Prevent back button to fall down to common handler - leaving
* application, so consume */
consumed = true;
}
return consumed;
}
static void bubble_animation_activate(BubbleAnimationView* view, bool force) {
furi_assert(view);
bool activate = true;
BubbleAnimationViewModel* model = view_get_model(view->view);
if(!model->current) {
activate = false;
} else if(model->freeze_frame) {
activate = false;
} else if(model->current->active_frames == 0) {
activate = false;
}
if(!force) {
if((model->active_ended_at + model->current->active_cooldown * 1000) >
xTaskGetTickCount()) {
activate = false;
} else if(model->active_shift) {
activate = false;
} else if(model->current_frame >= model->current->passive_frames) {
activate = false;
}
}
view_commit_model(view->view, false);
if(!activate && !force) {
return;
}
if(ACTIVE_SHIFT > 0) {
BubbleAnimationViewModel* model = view_get_model(view->view);
model->active_shift = ACTIVE_SHIFT;
view_commit_model(view->view, false);
} else {
bubble_animation_activate_right_now(view);
}
}
static void bubble_animation_activate_right_now(BubbleAnimationView* view) {
furi_assert(view);
uint8_t frame_rate = 0;
BubbleAnimationViewModel* model = view_get_model(view->view);
if(model->current && (model->current->active_frames > 0) && (!model->freeze_frame)) {
model->current_frame = model->current->passive_frames;
model->current_bubble = bubble_animation_pick_bubble(model, true);
frame_rate = model->current->frame_rate;
}
view_commit_model(view->view, true);
if(frame_rate) {
osTimerStart(view->timer, 1000 / frame_rate);
}
}
static void bubble_animation_next_frame(BubbleAnimationViewModel* model) {
furi_assert(model);
if(!model->current) {
return;
}
if(model->current_frame < model->current->passive_frames) {
model->current_frame = (model->current_frame + 1) % model->current->passive_frames;
} else {
++model->current_frame;
model->active_cycle +=
!((model->current_frame - model->current->passive_frames) %
model->current->active_frames);
if(model->active_cycle >= model->current->active_cycles) {
// switch to passive
model->active_cycle = 0;
model->current_frame = 0;
model->current_bubble = bubble_animation_pick_bubble(model, false);
model->active_ended_at = xTaskGetTickCount();
}
if(model->current_bubble) {
if(model->current_frame > model->current_bubble->ends_at_frame) {
model->current_bubble = model->current_bubble->next_bubble;
}
}
}
}
static void bubble_animation_timer_callback(void* context) {
furi_assert(context);
BubbleAnimationView* view = context;
bool activate = false;
BubbleAnimationViewModel* model = view_get_model(view->view);
if(model->active_shift > 0) {
activate = (--model->active_shift == 0);
}
if(!model->freeze_frame && !activate) {
bubble_animation_next_frame(model);
}
view_commit_model(view->view, !activate);
if(activate) {
bubble_animation_activate_right_now(view);
}
}
static Icon* bubble_animation_clone_frame(const Icon* icon_orig) {
furi_assert(icon_orig);
furi_assert(icon_orig->frames);
furi_assert(icon_orig->frames[0]);
Icon* icon_clone = furi_alloc(sizeof(Icon));
memcpy(icon_clone, icon_orig, sizeof(Icon));
icon_clone->frames = furi_alloc(sizeof(uint8_t*));
/* icon bitmap can be either compressed or not. It is compressed if
* compressed size is less than original, so max size for bitmap is
* uncompressed (width * height) + 1 byte (in uncompressed case)
* for compressed header
*/
size_t max_bitmap_size = ROUND_UP_TO(icon_orig->width, 8) * icon_orig->height + 1;
icon_clone->frames[0] = furi_alloc(max_bitmap_size);
memcpy((void*)icon_clone->frames[0], icon_orig->frames[0], max_bitmap_size);
return icon_clone;
}
static void bubble_animation_release_frame(Icon** icon) {
furi_assert(icon);
furi_assert(*icon);
free((void*)(*icon)->frames[0]);
free((*icon)->frames);
free(*icon);
*icon = NULL;
}
static void bubble_animation_enter(void* context) {
furi_assert(context);
BubbleAnimationView* view = context;
bubble_animation_activate(view, false);
BubbleAnimationViewModel* model = view_get_model(view->view);
uint8_t frame_rate = model->current->frame_rate;
view_commit_model(view->view, false);
if(frame_rate) {
osTimerStart(view->timer, 1000 / frame_rate);
}
}
static void bubble_animation_exit(void* context) {
furi_assert(context);
BubbleAnimationView* view = context;
osTimerStop(view->timer);
}
BubbleAnimationView* bubble_animation_view_alloc(void) {
BubbleAnimationView* view = furi_alloc(sizeof(BubbleAnimationView));
view->view = view_alloc();
view->interact_callback = NULL;
view->timer = osTimerNew(bubble_animation_timer_callback, osTimerPeriodic, view, NULL);
view_allocate_model(view->view, ViewModelTypeLocking, sizeof(BubbleAnimationViewModel));
view_set_context(view->view, view);
view_set_draw_callback(view->view, bubble_animation_draw_callback);
view_set_input_callback(view->view, bubble_animation_input_callback);
view_set_enter_callback(view->view, bubble_animation_enter);
view_set_exit_callback(view->view, bubble_animation_exit);
return view;
}
void bubble_animation_view_free(BubbleAnimationView* view) {
furi_assert(view);
view_set_draw_callback(view->view, NULL);
view_set_input_callback(view->view, NULL);
view_set_context(view->view, NULL);
view_free(view->view);
view->view = NULL;
free(view);
}
void bubble_animation_view_set_interact_callback(
BubbleAnimationView* view,
BubbleAnimationInteractCallback callback,
void* context) {
furi_assert(view);
view->interact_callback_context = context;
view->interact_callback = callback;
}
void bubble_animation_view_set_animation(
BubbleAnimationView* view,
const BubbleAnimation* new_animation) {
furi_assert(view);
furi_assert(new_animation);
BubbleAnimationViewModel* model = view_get_model(view->view);
furi_assert(model);
model->current = new_animation;
model->active_ended_at = xTaskGetTickCount() - (model->current->active_cooldown * 1000);
model->active_bubbles = 0;
model->passive_bubbles = 0;
for(int i = 0; i < new_animation->frame_bubbles_count; ++i) {
if(new_animation->frame_bubbles[i]->starts_at_frame < new_animation->passive_frames) {
++model->passive_bubbles;
} else {
++model->active_bubbles;
}
}
/* select bubble sequence */
model->current_bubble = bubble_animation_pick_bubble(model, false);
model->current_frame = 0;
model->active_cycle = 0;
view_commit_model(view->view, true);
osTimerStart(view->timer, 1000 / new_animation->frame_rate);
}
void bubble_animation_freeze(BubbleAnimationView* view) {
furi_assert(view);
BubbleAnimationViewModel* model = view_get_model(view->view);
furi_assert(model->current);
furi_assert(!model->freeze_frame);
/* always freeze first passive frame, because
* animation is always activated at unfreezing and played
* passive frame first, and 2 frames after - active
*/
uint8_t icon_index = 0;
model->freeze_frame = bubble_animation_clone_frame(model->current->icons[icon_index]);
model->current = NULL;
view_commit_model(view->view, false);
osTimerStop(view->timer);
}
void bubble_animation_unfreeze(BubbleAnimationView* view) {
furi_assert(view);
uint8_t frame_rate;
BubbleAnimationViewModel* model = view_get_model(view->view);
furi_assert(model->freeze_frame);
bubble_animation_release_frame(&model->freeze_frame);
furi_assert(model->current);
furi_assert(model->current->icons);
frame_rate = model->current->frame_rate;
view_commit_model(view->view, true);
osTimerStart(view->timer, 1000 / frame_rate);
bubble_animation_activate(view, false);
}
View* bubble_animation_get_view(BubbleAnimationView* view) {
furi_assert(view);
return view->view;
}

View File

@ -0,0 +1,89 @@
#pragma once
#include <gui/view.h>
#include "../animation_manager.h"
/** Bubble Animation instance */
typedef struct BubbleAnimationView BubbleAnimationView;
/** Callback type to be called when interact button pressed */
typedef void (*BubbleAnimationInteractCallback)(void*);
/**
* Allocate bubble animation view.
* This is animation with bubbles, and 2 phases:
* active and passive.
*
* @return instance of new bubble animation
*/
BubbleAnimationView* bubble_animation_view_alloc(void);
/**
* Free bubble animation view.
*
* @view bubble animation view instance
*/
void bubble_animation_view_free(BubbleAnimationView* view);
/**
* Set callback for interact action for animation.
* Currently this is right button.
*
* @view bubble animation view instance
* @callback callback to call when button pressed
* @context context
*/
void bubble_animation_view_set_interact_callback(
BubbleAnimationView* view,
BubbleAnimationInteractCallback callback,
void* context);
/**
* Set new animation.
* BubbleAnimation doesn't posses Bubble Animation object
* so it doesn't handle any memory manipulation on Bubble Animations.
*
* @view bubble animation view instance
* @new_bubble_animation new animation to set
*/
void bubble_animation_view_set_animation(
BubbleAnimationView* view,
const BubbleAnimation* new_bubble_animation);
/**
* Get view of bubble animation.
*
* @view bubble animation view instance
* @return view
*/
View* bubble_animation_get_view(BubbleAnimationView* view);
/**
* Freeze current playing animation. Saves a frame to be shown
* during next unfreeze called.
* bubble_animation_freeze() stops any reference to 'current' animation
* so it can be freed. Therefore lock unfreeze should be preceeded with
* new animation set.
*
* Freeze/Unfreeze usage example:
*
* animation_view_alloc()
* set_animation()
* ...
* freeze_animation()
* // release animation
* ...
* // allocate animation
* set_animation()
* unfreeze()
*
* @view bubble animation view instance
*/
void bubble_animation_freeze(BubbleAnimationView* view);
/**
* Starts bubble animation after freezing.
*
* @view bubble animation view instance
*/
void bubble_animation_unfreeze(BubbleAnimationView* view);

View File

@ -2,15 +2,16 @@
#include "cmsis_os2.h"
#include "desktop/desktop.h"
#include "desktop_i.h"
#include "gui/view_composed.h"
#include <dolphin/dolphin.h>
#include <furi/pubsub.h>
#include <furi/record.h>
#include "portmacro.h"
#include "storage/filesystem-api-defines.h"
#include "storage/filesystem_api_defines.h"
#include "storage/storage.h"
#include <stdint.h>
#include <power/power_service/power.h>
#include "helpers/desktop_animation.h"
#include "animations/animation_manager.h"
static void desktop_lock_icon_callback(Canvas* canvas, void* context) {
furi_assert(canvas);
@ -32,11 +33,12 @@ bool desktop_back_event_callback(void* context) {
Desktop* desktop_alloc() {
Desktop* desktop = furi_alloc(sizeof(Desktop));
desktop->unload_animation_semaphore = osSemaphoreNew(1, 0, NULL);
desktop->animation_manager = animation_manager_alloc();
desktop->gui = furi_record_open("gui");
desktop->scene_thread = furi_thread_alloc();
desktop->view_dispatcher = view_dispatcher_alloc();
desktop->scene_manager = scene_manager_alloc(&desktop_scene_handlers, desktop);
desktop->animation = desktop_animation_alloc();
view_dispatcher_enable_queue(desktop->view_dispatcher);
view_dispatcher_attach_to_gui(
@ -48,16 +50,34 @@ Desktop* desktop_alloc() {
view_dispatcher_set_navigation_event_callback(
desktop->view_dispatcher, desktop_back_event_callback);
desktop->dolphin_view = animation_manager_get_animation_view(desktop->animation_manager);
desktop->main_view_composed = view_composed_alloc();
desktop->main_view = desktop_main_alloc();
desktop->lock_menu = desktop_lock_menu_alloc();
view_composed_tie_views(
desktop->main_view_composed,
desktop->dolphin_view,
desktop_main_get_view(desktop->main_view));
view_composed_top_enable(desktop->main_view_composed, true);
desktop->locked_view_composed = view_composed_alloc();
desktop->locked_view = desktop_locked_alloc();
view_composed_tie_views(
desktop->locked_view_composed,
desktop->dolphin_view,
desktop_locked_get_view(desktop->locked_view));
view_composed_top_enable(desktop->locked_view_composed, true);
desktop->lock_menu = desktop_lock_menu_alloc();
desktop->debug_view = desktop_debug_alloc();
desktop->first_start_view = desktop_first_start_alloc();
desktop->hw_mismatch_popup = popup_alloc();
desktop->code_input = code_input_alloc();
view_dispatcher_add_view(
desktop->view_dispatcher, DesktopViewMain, desktop_main_get_view(desktop->main_view));
desktop->view_dispatcher,
DesktopViewMain,
view_composed_get_view(desktop->main_view_composed));
view_dispatcher_add_view(
desktop->view_dispatcher,
DesktopViewLockMenu,
@ -67,7 +87,7 @@ Desktop* desktop_alloc() {
view_dispatcher_add_view(
desktop->view_dispatcher,
DesktopViewLocked,
desktop_locked_get_view(desktop->locked_view));
view_composed_get_view(desktop->locked_view_composed));
view_dispatcher_add_view(
desktop->view_dispatcher,
DesktopViewFirstStart,
@ -91,7 +111,6 @@ Desktop* desktop_alloc() {
void desktop_free(Desktop* desktop) {
furi_assert(desktop);
desktop_animation_free(desktop->animation);
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewMain);
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLockMenu);
view_dispatcher_remove_view(desktop->view_dispatcher, DesktopViewLocked);
@ -103,6 +122,9 @@ void desktop_free(Desktop* desktop) {
view_dispatcher_free(desktop->view_dispatcher);
scene_manager_free(desktop->scene_manager);
animation_manager_free(desktop->animation_manager);
view_composed_free(desktop->main_view_composed);
view_composed_free(desktop->locked_view_composed);
desktop_main_free(desktop->main_view);
desktop_lock_menu_free(desktop->lock_menu);
desktop_locked_free(desktop->locked_view);
@ -111,6 +133,8 @@ void desktop_free(Desktop* desktop) {
popup_free(desktop->hw_mismatch_popup);
code_input_free(desktop->code_input);
osSemaphoreDelete(desktop->unload_animation_semaphore);
furi_record_close("gui");
desktop->gui = NULL;
@ -129,29 +153,9 @@ static bool desktop_is_first_start() {
return exists;
}
static void desktop_dolphin_state_changed_callback(const void* message, void* context) {
Desktop* desktop = context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventUpdateAnimation);
}
static void desktop_storage_state_changed_callback(const void* message, void* context) {
Desktop* desktop = context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventUpdateAnimation);
}
int32_t desktop_srv(void* p) {
Desktop* desktop = desktop_alloc();
Dolphin* dolphin = furi_record_open("dolphin");
FuriPubSub* dolphin_pubsub = dolphin_get_pubsub(dolphin);
FuriPubSubSubscription* dolphin_subscription =
furi_pubsub_subscribe(dolphin_pubsub, desktop_dolphin_state_changed_callback, desktop);
Storage* storage = furi_record_open("storage");
FuriPubSub* storage_pubsub = storage_get_pubsub(storage);
FuriPubSubSubscription* storage_subscription =
furi_pubsub_subscribe(storage_pubsub, desktop_storage_state_changed_callback, desktop);
bool loaded = LOAD_DESKTOP_SETTINGS(&desktop->settings);
if(!loaded) {
furi_hal_rtc_reset_flag(FuriHalRtcFlagLock);
@ -176,9 +180,11 @@ int32_t desktop_srv(void* p) {
scene_manager_next_scene(desktop->scene_manager, DesktopSceneHwMismatch);
}
if(furi_hal_rtc_get_fault_data()) {
scene_manager_next_scene(desktop->scene_manager, DesktopSceneFault);
}
view_dispatcher_run(desktop->view_dispatcher);
furi_pubsub_unsubscribe(dolphin_pubsub, dolphin_subscription);
furi_pubsub_unsubscribe(storage_pubsub, storage_subscription);
desktop_free(desktop);
return 0;

View File

@ -1,9 +1,12 @@
#pragma once
#include "cmsis_os2.h"
#include "desktop.h"
#include "animations/animation_manager.h"
#include "gui/view_composed.h"
#include <furi.h>
#include <furi-hal.h>
#include <furi_hal.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
@ -21,7 +24,6 @@
#include "views/desktop_debug.h"
#include "scenes/desktop_scene.h"
#include "helpers/desktop_animation.h"
#include "desktop/desktop_settings/desktop_settings.h"
#include <gui/icon.h>
@ -46,19 +48,29 @@ struct Desktop {
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
DesktopAnimation* animation;
DesktopFirstStartView* first_start_view;
Popup* hw_mismatch_popup;
DesktopMainView* main_view;
DesktopLockMenuView* lock_menu;
DesktopLockedView* locked_view;
DesktopDebugView* debug_view;
CodeInput* code_input;
View* dolphin_view;
DesktopMainView* main_view;
DesktopLockedView* locked_view;
ViewComposed* main_view_composed;
ViewComposed* locked_view_composed;
DesktopSettings settings;
PinCode pincode_buffer;
ViewPort* lock_viewport;
AnimationManager* animation_manager;
osSemaphoreId_t unload_animation_semaphore;
FuriPubSubSubscription* app_start_stop_subscription;
char* text_buffer;
};
Desktop* desktop_alloc();

View File

@ -1,382 +0,0 @@
#include "desktop/helpers/desktop_animation.h"
#include "assets_icons.h"
#include "desktop_animation_i.h"
#include "cmsis_os2.h"
#include "furi/common_defines.h"
#include "furi/record.h"
#include "storage/filesystem-api-defines.h"
#include <power/power_service/power.h>
#include <m-list.h>
#include <storage/storage.h>
#include <desktop/desktop.h>
#include <dolphin/dolphin.h>
#define KEEP_ONLY_CALM_BASIC_ANIMATIONS 1
LIST_DEF(AnimationList, const PairedAnimation*, M_PTR_OPLIST)
#define M_OPL_AnimationList_t() LIST_OPLIST(AnimationList)
#define PUSH_BACK_ANIMATIONS(listname, animations, butthurt) \
for(int i = 0; i < COUNT_OF(animations); ++i) { \
if(!(animations)[i].basic->butthurt_level_mask || \
((animations)[i].basic->butthurt_level_mask & BUTTHURT_LEVEL(butthurt))) { \
AnimationList_push_back(animation_list, &(animations)[i]); \
} \
}
#define IS_BLOCKING_ANIMATION(x) \
(((x) != DesktopAnimationStateBasic) && ((x) != DesktopAnimationStateActive))
#define IS_ONESHOT_ANIMATION(x) ((x) == DesktopAnimationStateLevelUpIsPending)
static void desktop_animation_timer_callback(void* context);
struct DesktopAnimation {
bool sd_shown_error_db;
bool sd_shown_error_card_bad;
osTimerId_t timer;
const PairedAnimation* current;
const Icon* current_blocking_icon;
const Icon** current_one_shot_icons;
uint8_t one_shot_animation_counter;
uint8_t one_shot_animation_size;
DesktopAnimationState state;
TickType_t basic_started_at;
TickType_t active_finished_at;
AnimationChangedCallback animation_changed_callback;
void* animation_changed_callback_context;
};
DesktopAnimation* desktop_animation_alloc(void) {
DesktopAnimation* animation = furi_alloc(sizeof(DesktopAnimation));
animation->timer = osTimerNew(
desktop_animation_timer_callback, osTimerPeriodic /* osTimerOnce */, animation, NULL);
animation->active_finished_at = (TickType_t)(-30);
animation->basic_started_at = 0;
animation->animation_changed_callback = NULL;
animation->animation_changed_callback_context = NULL;
desktop_start_new_idle_animation(animation);
return animation;
}
void desktop_animation_free(DesktopAnimation* animation) {
furi_assert(animation);
osTimerDelete(animation->timer);
free(animation);
}
void desktop_animation_set_animation_changed_callback(
DesktopAnimation* animation,
AnimationChangedCallback callback,
void* context) {
furi_assert(animation);
animation->animation_changed_callback = callback;
animation->animation_changed_callback_context = context;
}
void desktop_start_new_idle_animation(DesktopAnimation* animation) {
Dolphin* dolphin = furi_record_open("dolphin");
DolphinStats stats = dolphin_stats(dolphin);
furi_record_close("dolphin");
furi_check((stats.level >= 1) && (stats.level <= 3));
AnimationList_t animation_list;
AnimationList_init(animation_list);
#if KEEP_ONLY_CALM_BASIC_ANIMATIONS
PUSH_BACK_ANIMATIONS(animation_list, calm_animation, 0);
#else
PUSH_BACK_ANIMATIONS(animation_list, calm_animation, stats.butthurt);
PUSH_BACK_ANIMATIONS(animation_list, mad_animation, stats.butthurt);
switch(stats.level) {
case 1:
PUSH_BACK_ANIMATIONS(animation_list, level_1_animation, stats.butthurt);
break;
case 2:
PUSH_BACK_ANIMATIONS(animation_list, level_2_animation, stats.butthurt);
break;
case 3:
PUSH_BACK_ANIMATIONS(animation_list, level_3_animation, stats.butthurt);
break;
default:
furi_crash("Dolphin level is out of bounds");
}
Power* power = furi_record_open("power");
PowerInfo info;
power_get_info(power, &info);
if(!power_is_battery_well(&info)) {
PUSH_BACK_ANIMATIONS(animation_list, check_battery_animation, stats.butthurt);
}
Storage* storage = furi_record_open("storage");
FS_Error sd_status = storage_sd_status(storage);
animation->current = NULL;
if(sd_status == FSE_NOT_READY) {
PUSH_BACK_ANIMATIONS(animation_list, no_sd_animation, stats.butthurt);
animation->sd_shown_error_card_bad = false;
animation->sd_shown_error_db = false;
}
#endif
uint32_t whole_weight = 0;
for
M_EACH(item, animation_list, AnimationList_t) {
whole_weight += (*item)->basic->weight;
}
uint32_t lucky_number = random() % whole_weight;
uint32_t weight = 0;
const PairedAnimation* selected = NULL;
for
M_EACH(item, animation_list, AnimationList_t) {
if(lucky_number < weight) {
break;
}
weight += (*item)->basic->weight;
selected = *item;
}
animation->basic_started_at = osKernelGetTickCount();
animation->current = selected;
osTimerStart(animation->timer, animation->current->basic->duration * 1000);
animation->state = DesktopAnimationStateBasic;
furi_assert(selected);
AnimationList_clear(animation_list);
}
static void desktop_animation_timer_callback(void* context) {
furi_assert(context);
DesktopAnimation* animation = context;
TickType_t now_ms = osKernelGetTickCount();
AnimationList_t animation_list;
AnimationList_init(animation_list);
bool new_basic_animation = false;
if(animation->state == DesktopAnimationStateActive) {
animation->state = DesktopAnimationStateBasic;
TickType_t basic_lasts_ms = now_ms - animation->basic_started_at;
animation->active_finished_at = now_ms;
TickType_t basic_duration_ms = animation->current->basic->duration * 1000;
if(basic_lasts_ms > basic_duration_ms) {
// if active animation finished, and basic duration came to an end
// select new idle animation
new_basic_animation = true;
} else {
// if active animation finished, but basic duration is not finished
// play current animation for the rest of time
furi_assert(basic_duration_ms != basic_lasts_ms);
osTimerStart(animation->timer, basic_duration_ms - basic_lasts_ms);
}
} else if(animation->state == DesktopAnimationStateBasic) {
// if basic animation finished
// select new idle animation
new_basic_animation = true;
}
if(new_basic_animation) {
animation->basic_started_at = now_ms;
desktop_start_new_idle_animation(animation);
}
// for oneshot generate events every time
if(animation->animation_changed_callback) {
animation->animation_changed_callback(animation->animation_changed_callback_context);
}
}
void desktop_animation_activate(DesktopAnimation* animation) {
furi_assert(animation);
if(animation->state != DesktopAnimationStateBasic) {
return;
}
if(animation->state == DesktopAnimationStateActive) {
return;
}
if(!animation->current->active) {
return;
}
TickType_t now = osKernelGetTickCount();
TickType_t time_since_last_active = now - animation->active_finished_at;
if(time_since_last_active > (animation->current->basic->active_cooldown * 1000)) {
animation->state = DesktopAnimationStateActive;
furi_assert(animation->current->active->duration > 0);
osTimerStart(animation->timer, animation->current->active->duration * 1000);
if(animation->animation_changed_callback) {
animation->animation_changed_callback(animation->animation_changed_callback_context);
}
}
}
static const Icon* desktop_animation_get_current_idle_animation(
DesktopAnimation* animation,
bool* status_bar_background_black) {
const ActiveAnimation* active = animation->current->active;
const BasicAnimation* basic = animation->current->basic;
if(animation->state == DesktopAnimationStateActive && active->icon) {
*status_bar_background_black = active->black_status_bar;
return active->icon;
} else {
*status_bar_background_black = basic->black_status_bar;
return basic->icon;
}
}
// Every time somebody starts 'desktop_animation_get_animation()'
// 1) check if there is a new level
// 2) check if there is SD card corruption
// 3) check if the SD card is empty
// 4) if all false - get idle animation
const Icon* desktop_animation_get_animation(
DesktopAnimation* animation,
bool* status_bar_background_black) {
Dolphin* dolphin = furi_record_open("dolphin");
Storage* storage = furi_record_open("storage");
const Icon* icon = NULL;
furi_assert(animation);
FS_Error sd_status = storage_sd_status(storage);
if(IS_BLOCKING_ANIMATION(animation->state)) {
// don't give new animation till blocked animation
// is reseted
icon = animation->current_blocking_icon;
}
if(!icon) {
if(sd_status == FSE_INTERNAL) {
osTimerStop(animation->timer);
icon = &A_CardBad_128x51;
animation->current_blocking_icon = icon;
animation->state = DesktopAnimationStateSDCorrupted;
animation->sd_shown_error_card_bad = true;
animation->sd_shown_error_db = false;
} else if(sd_status == FSE_NOT_READY) {
animation->sd_shown_error_card_bad = false;
animation->sd_shown_error_db = false;
} else if(sd_status == FSE_OK) {
bool db_exists = storage_common_stat(storage, "/ext/manifest.txt", NULL) == FSE_OK;
if(db_exists && !animation->sd_shown_error_db) {
osTimerStop(animation->timer);
icon = &A_CardNoDB_128x51;
animation->current_blocking_icon = icon;
animation->state = DesktopAnimationStateSDEmpty;
animation->sd_shown_error_db = true;
}
}
}
DolphinStats stats = dolphin_stats(dolphin);
if(!icon && stats.level_up_is_pending) {
osTimerStop(animation->timer);
icon = &A_LevelUpPending_128x51;
animation->current_blocking_icon = icon;
animation->state = DesktopAnimationStateLevelUpIsPending;
}
if(!icon) {
icon =
desktop_animation_get_current_idle_animation(animation, status_bar_background_black);
} else {
status_bar_background_black = false;
}
furi_record_close("storage");
furi_record_close("dolphin");
return icon;
}
DesktopAnimationState desktop_animation_handle_right(DesktopAnimation* animation) {
furi_assert(animation);
bool reset_animation = false;
bool update_animation = false;
switch(animation->state) {
case DesktopAnimationStateActive:
case DesktopAnimationStateBasic:
/* nothing */
break;
case DesktopAnimationStateLevelUpIsPending:
/* do nothing, main scene should change itself */
break;
case DesktopAnimationStateSDCorrupted:
reset_animation = true;
break;
case DesktopAnimationStateSDEmpty:
animation->state = DesktopAnimationStateSDEmptyURL;
animation->current_blocking_icon = &A_CardNoDBUrl_128x51;
update_animation = true;
break;
case DesktopAnimationStateSDEmptyURL:
reset_animation = true;
break;
default:
furi_crash("Unhandled desktop animation state");
}
if(reset_animation) {
desktop_start_new_idle_animation(animation);
update_animation = true;
}
if(update_animation) {
if(animation->animation_changed_callback) {
animation->animation_changed_callback(animation->animation_changed_callback_context);
}
}
return animation->state;
}
#define LEVELUP_FRAME_RATE (0.2)
void desktop_animation_start_oneshot_levelup(DesktopAnimation* animation) {
animation->one_shot_animation_counter = 0;
animation->state = DesktopAnimationStateLevelUpIsPending;
Dolphin* dolphin = furi_record_open("dolphin");
DolphinStats stats = dolphin_stats(dolphin);
furi_record_close("dolphin");
furi_assert(stats.level_up_is_pending);
if(stats.level == 1) {
animation->current_one_shot_icons = animation_level2up;
animation->one_shot_animation_size = COUNT_OF(animation_level2up);
} else if(stats.level == 2) {
animation->current_one_shot_icons = animation_level3up;
animation->one_shot_animation_size = COUNT_OF(animation_level3up);
} else {
furi_crash("Dolphin level is out of bounds");
}
osTimerStart(animation->timer, LEVELUP_FRAME_RATE * 1000);
}
const Icon* desktop_animation_get_oneshot_frame(DesktopAnimation* animation) {
furi_assert(IS_ONESHOT_ANIMATION(animation->state));
furi_assert(animation->one_shot_animation_size > 0);
const Icon* icon = NULL;
if(animation->one_shot_animation_counter < animation->one_shot_animation_size) {
icon = animation->current_one_shot_icons[animation->one_shot_animation_counter];
++animation->one_shot_animation_counter;
} else {
animation->state = DesktopAnimationStateBasic;
animation->one_shot_animation_size = 0;
osTimerStop(animation->timer);
icon = NULL;
}
return icon;
}

View File

@ -1,59 +0,0 @@
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include <gui/icon.h>
typedef struct DesktopAnimation DesktopAnimation;
typedef struct ActiveAnimation ActiveAnimation;
typedef struct BasicAnimation BasicAnimation;
typedef enum {
DesktopAnimationStateBasic,
DesktopAnimationStateActive,
DesktopAnimationStateLevelUpIsPending,
DesktopAnimationStateSDEmpty,
DesktopAnimationStateSDEmptyURL,
DesktopAnimationStateSDCorrupted,
} DesktopAnimationState;
struct BasicAnimation {
const Icon* icon;
uint16_t duration; // sec
uint16_t active_cooldown;
uint8_t weight;
bool black_status_bar;
uint16_t butthurt_level_mask;
};
struct ActiveAnimation {
const Icon* icon;
bool black_status_bar;
uint16_t duration; // sec
};
typedef struct {
const BasicAnimation* basic;
const ActiveAnimation* active;
} PairedAnimation;
typedef void (*AnimationChangedCallback)(void*);
DesktopAnimation* desktop_animation_alloc(void);
void desktop_animation_free(DesktopAnimation*);
void desktop_animation_activate(DesktopAnimation* instance);
void desktop_animation_set_animation_changed_callback(
DesktopAnimation* instance,
AnimationChangedCallback callback,
void* context);
DesktopAnimationState desktop_animation_handle_right(DesktopAnimation* animation);
void desktop_animation_start_oneshot_levelup(DesktopAnimation* animation);
const Icon*
desktop_animation_get_animation(DesktopAnimation* animation, bool* status_bar_background_black);
const Icon* desktop_animation_get_oneshot_frame(DesktopAnimation* animation);
void desktop_start_new_idle_animation(DesktopAnimation* animation);

View File

@ -1,332 +0,0 @@
#include <assets_icons.h>
#include <stddef.h>
#include <stdint.h>
#include <gui/icon.h>
#include "desktop_animation.h"
// Calm/Mad Basic Idle Animations
#define COMMON_BASIC_DURATION (2 * 60 * 60)
#define COMMON_ACTIVE_CYCLES 1
#define COMMON_ACTIVE_COOLDOWN 15
#define COMMON_WEIGHT 3
#define BUTTHURT_LEVEL(x) (1UL << (x))
#define BUTTHURT_LEVEL_0 0
// frames * cycles / frame_rate
#define COMMON_ACTIVE_DURATION(x) ((x)*COMMON_ACTIVE_CYCLES / 2)
static const BasicAnimation animation_TV = {
.icon = &A_Tv_128x52,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7)};
static const ActiveAnimation animation_TV_active = {
.icon = &A_TvActive_128x52,
.duration = COMMON_ACTIVE_DURATION(6),
};
static const BasicAnimation animation_sleep = {
.icon = &A_Sleep_128x52,
.black_status_bar = true,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) |
BUTTHURT_LEVEL(9) | BUTTHURT_LEVEL(10)};
static const ActiveAnimation animation_sleep_active = {
.icon = &A_SleepActive_128x52,
.black_status_bar = true,
.duration = COMMON_ACTIVE_DURATION(5),
};
static const BasicAnimation animation_leaving = {
.icon = &A_Leaving_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(13) | BUTTHURT_LEVEL(14),
};
static const ActiveAnimation animation_leaving_active = {
.icon = &A_LeavingActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
static const BasicAnimation animation_laptop = {
.icon = &A_Laptop_128x52,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5)};
static const ActiveAnimation animation_laptop_active = {
.icon = &A_LaptopActive_128x52,
.duration = COMMON_ACTIVE_DURATION(8),
};
static const BasicAnimation animation_knife = {
.icon = &A_Knife_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(5) | BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) |
BUTTHURT_LEVEL(8) | BUTTHURT_LEVEL(9) | BUTTHURT_LEVEL(10) |
BUTTHURT_LEVEL(11) | BUTTHURT_LEVEL(12) | BUTTHURT_LEVEL(13)};
static const ActiveAnimation animation_knife_active = {
.icon = &A_KnifeActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
static const BasicAnimation animation_cry = {
.icon = &A_Cry_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) |
BUTTHURT_LEVEL(9) | BUTTHURT_LEVEL(10) | BUTTHURT_LEVEL(11) |
BUTTHURT_LEVEL(12) | BUTTHURT_LEVEL(13)};
static const ActiveAnimation animation_cry_active = {
.icon = &A_CryActive_128x51,
.duration = COMMON_ACTIVE_DURATION(3),
};
static const BasicAnimation animation_box = {
.icon = &A_Box_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) | BUTTHURT_LEVEL(9) |
BUTTHURT_LEVEL(10) | BUTTHURT_LEVEL(11) | BUTTHURT_LEVEL(12) |
BUTTHURT_LEVEL(13)};
static const ActiveAnimation animation_box_active = {
.icon = &A_BoxActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
static const BasicAnimation animation_waves = {
.icon = &A_Waves_128x52,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2)};
static const ActiveAnimation animation_waves_active = {
.icon = &A_WavesActive_128x52,
.duration = COMMON_ACTIVE_DURATION(7),
};
// Level Idle Animations
static const BasicAnimation animation_level1furippa = {
.icon = &A_Level1Furippa_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7)};
static const ActiveAnimation animation_level1furippa_active = {
.icon = &A_Level1FurippaActive_128x51,
.duration = COMMON_ACTIVE_DURATION(6),
};
static const BasicAnimation animation_level1read = {
.icon = &A_Level1Read_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2)};
static const ActiveAnimation animation_level1read_active = {
.icon = &A_Level1ReadActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
static const BasicAnimation animation_level1toys = {
.icon = &A_Level1Toys_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8)};
static const ActiveAnimation animation_level1toys_active = {
.icon = &A_Level1ToysActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
static const BasicAnimation animation_level2furippa = {
.icon = &A_Level2Furippa_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7)};
static const ActiveAnimation animation_level2furippa_active = {
.icon = &A_Level2FurippaActive_128x51,
.duration = COMMON_ACTIVE_DURATION(6),
};
static const BasicAnimation animation_level2soldering = {
.icon = &A_Level2Soldering_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) |
BUTTHURT_LEVEL(9)};
static const ActiveAnimation animation_level2soldering_active = {
.icon = &A_Level2SolderingActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
static const BasicAnimation animation_level2hack = {
.icon = &A_Level2Hack_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8)};
static const ActiveAnimation animation_level2hack_active = {
.icon = &A_Level2HackActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
static const BasicAnimation animation_level3furippa = {
.icon = &A_Level3Furippa_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7)};
static const ActiveAnimation animation_level3furippa_active = {
.icon = &A_Level3FurippaActive_128x51,
.duration = COMMON_ACTIVE_DURATION(6),
};
static const BasicAnimation animation_level3hijack = {
.icon = &A_Level3Hijack_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8) |
BUTTHURT_LEVEL(9)};
static const ActiveAnimation animation_level3hijack_active = {
.icon = &A_Level3HijackActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
static const BasicAnimation animation_level3lab = {
.icon = &A_Level3Lab_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = COMMON_WEIGHT,
.active_cooldown = COMMON_ACTIVE_COOLDOWN,
.butthurt_level_mask = BUTTHURT_LEVEL(0) | BUTTHURT_LEVEL(1) | BUTTHURT_LEVEL(2) |
BUTTHURT_LEVEL(3) | BUTTHURT_LEVEL(4) | BUTTHURT_LEVEL(5) |
BUTTHURT_LEVEL(6) | BUTTHURT_LEVEL(7) | BUTTHURT_LEVEL(8)};
static const ActiveAnimation animation_level3lab_active = {
.icon = &A_Level3LabActive_128x51,
.duration = COMMON_ACTIVE_DURATION(2),
};
// System Idle Animations
static const BasicAnimation animation_bad_battery = {
.icon = &A_BadBattery_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = 7,
};
static const BasicAnimation animation_no_sd_card = {
.icon = &A_NoSdCard_128x51,
.duration = COMMON_BASIC_DURATION,
.weight = 7,
};
const Icon* animation_level2up[] = {
&I_LevelUp2_01,
&I_LevelUp2_02,
&I_LevelUp2_03,
&I_LevelUp2_04,
&I_LevelUp2_05,
&I_LevelUp2_06,
&I_LevelUp2_07};
const Icon* animation_level3up[] = {
&I_LevelUp3_01,
&I_LevelUp3_02,
&I_LevelUp3_03,
&I_LevelUp3_04,
&I_LevelUp3_05,
&I_LevelUp3_06,
&I_LevelUp3_07};
// Blocking Idle Animations & One shot Animations represented as naked Icon
static const PairedAnimation calm_animation[] = {
{.basic = &animation_TV, .active = &animation_TV_active},
{.basic = &animation_waves, .active = &animation_waves_active},
{.basic = &animation_sleep, .active = &animation_sleep_active},
{.basic = &animation_laptop, .active = &animation_laptop_active},
};
static const PairedAnimation mad_animation[] = {
{.basic = &animation_cry, .active = &animation_cry_active},
{.basic = &animation_knife, .active = &animation_knife_active},
{.basic = &animation_box, .active = &animation_box_active},
{.basic = &animation_leaving, .active = &animation_leaving_active},
};
static const PairedAnimation level_1_animation[] = {
{.basic = &animation_level1furippa, .active = &animation_level1furippa_active},
{.basic = &animation_level1read, .active = &animation_level1read_active},
{.basic = &animation_level1toys, .active = &animation_level1toys_active},
};
static const PairedAnimation level_2_animation[] = {
{.basic = &animation_level2furippa, .active = &animation_level2furippa_active},
{.basic = &animation_level2soldering, .active = &animation_level2soldering_active},
{.basic = &animation_level2hack, .active = &animation_level2hack_active},
};
static const PairedAnimation level_3_animation[] = {
{.basic = &animation_level3furippa, .active = &animation_level3furippa_active},
{.basic = &animation_level3hijack, .active = &animation_level3hijack_active},
{.basic = &animation_level3lab, .active = &animation_level3lab_active},
};
static const PairedAnimation no_sd_animation[] = {
{.basic = &animation_no_sd_card, .active = NULL},
};
static const PairedAnimation check_battery_animation[] = {
{.basic = &animation_bad_battery, .active = NULL},
};

View File

@ -5,4 +5,4 @@ ADD_SCENE(desktop, debug, Debug)
ADD_SCENE(desktop, first_start, FirstStart)
ADD_SCENE(desktop, hw_mismatch, HwMismatch)
ADD_SCENE(desktop, pinsetup, PinSetup)
ADD_SCENE(desktop, levelup, LevelUp)
ADD_SCENE(desktop, fault, Fault)

View File

@ -3,7 +3,7 @@
#include <dolphin/dolphin.h>
#include <dolphin/helpers/dolphin_deed.h>
void desktop_scene_debug_callback(DesktopDebugEvent event, void* context) {
void desktop_scene_debug_callback(DesktopEvent event, void* context) {
Desktop* desktop = (Desktop*)context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
}
@ -33,14 +33,12 @@ bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) {
case DesktopDebugEventDeed:
dolphin_deed(dolphin, DolphinDeedIButtonEmulate);
desktop_debug_get_dolphin_data(desktop->debug_view);
desktop_start_new_idle_animation(desktop->animation);
consumed = true;
break;
case DesktopDebugEventWrongDeed:
dolphin_deed(dolphin, DolphinDeedWrong);
desktop_debug_get_dolphin_data(desktop->debug_view);
desktop_start_new_idle_animation(desktop->animation);
consumed = true;
break;

View File

@ -0,0 +1,49 @@
#include "../desktop_i.h"
#define DesktopFaultEventExit 0x00FF00FF
void desktop_scene_fault_callback(void* context) {
Desktop* desktop = (Desktop*)context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopFaultEventExit);
}
void desktop_scene_fault_on_enter(void* context) {
Desktop* desktop = (Desktop*)context;
Popup* popup = desktop->hw_mismatch_popup;
popup_set_context(popup, desktop);
popup_set_header(
popup,
"Flipper crashed\n and was rebooted",
60,
14 + STATUS_BAR_Y_SHIFT,
AlignCenter,
AlignCenter);
char* message = (char*)furi_hal_rtc_get_fault_data();
popup_set_text(popup, message, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
popup_set_callback(popup, desktop_scene_fault_callback);
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewHwMismatch);
}
bool desktop_scene_fault_on_event(void* context, SceneManagerEvent event) {
Desktop* desktop = (Desktop*)context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case DesktopFaultEventExit:
scene_manager_previous_scene(desktop->scene_manager);
consumed = true;
break;
default:
break;
}
}
return consumed;
}
void desktop_scene_fault_on_exit(void* context) {
furi_hal_rtc_set_fault_data(0);
}

View File

@ -1,7 +1,8 @@
#include "../desktop_i.h"
#include "../views/desktop_first_start.h"
#include "../views/desktop_events.h"
void desktop_scene_first_start_callback(DesktopFirstStartEvent event, void* context) {
void desktop_scene_first_start_callback(DesktopEvent event, void* context) {
Desktop* desktop = (Desktop*)context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
}

View File

@ -1,5 +1,5 @@
#include "../desktop_i.h"
#include <furi-hal-version.h>
#include <furi_hal_version.h>
#define HW_MISMATCH_BACK_EVENT (0UL)
@ -10,18 +10,21 @@ void desktop_scene_hw_mismatch_callback(void* context) {
void desktop_scene_hw_mismatch_on_enter(void* context) {
Desktop* desktop = (Desktop*)context;
furi_assert(desktop);
furi_assert(!desktop->text_buffer);
Popup* popup = desktop->hw_mismatch_popup;
char buffer[256]; // strange but smaller buffer not making it
desktop->text_buffer = furi_alloc(256);
snprintf(
buffer,
sizeof(buffer),
desktop->text_buffer,
256,
"HW target: %d\nFW target: %d",
furi_hal_version_get_hw_target(),
version_get_target(NULL));
popup_set_context(popup, desktop);
popup_set_header(
popup, "!!!! HW Mismatch !!!!", 60, 14 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
popup_set_text(popup, buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
popup_set_text(
popup, desktop->text_buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter);
popup_set_callback(popup, desktop_scene_hw_mismatch_callback);
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewHwMismatch);
}
@ -46,9 +49,13 @@ bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event)
void desktop_scene_hw_mismatch_on_exit(void* context) {
Desktop* desktop = (Desktop*)context;
furi_assert(desktop);
furi_assert(desktop->text_buffer);
Popup* popup = desktop->hw_mismatch_popup;
popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom);
popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop);
popup_set_callback(popup, NULL);
popup_set_context(popup, NULL);
free(desktop->text_buffer);
desktop->text_buffer = NULL;
}

View File

@ -1,79 +0,0 @@
#include "../desktop_i.h"
#include "../views/desktop_main.h"
#include "applications.h"
#include "assets_icons.h"
#include "desktop/desktop.h"
#include "desktop/helpers/desktop_animation.h"
#include "dolphin/dolphin.h"
#include "furi/pubsub.h"
#include "furi/record.h"
#include "storage/storage-glue.h"
#include <loader/loader.h>
#include <m-list.h>
#define LEVELUP_SCENE_PLAYING 0
#define LEVELUP_SCENE_STOPPED 1
static void desktop_scene_levelup_callback(DesktopMainEvent event, void* context) {
Desktop* desktop = (Desktop*)context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
}
static void desktop_scene_levelup_animation_changed_callback(void* context) {
furi_assert(context);
Desktop* desktop = context;
view_dispatcher_send_custom_event(
desktop->view_dispatcher, DesktopMainEventUpdateOneShotAnimation);
}
void desktop_scene_levelup_on_enter(void* context) {
Desktop* desktop = (Desktop*)context;
DesktopMainView* main_view = desktop->main_view;
desktop_main_set_callback(main_view, desktop_scene_levelup_callback, desktop);
desktop_animation_set_animation_changed_callback(
desktop->animation, desktop_scene_levelup_animation_changed_callback, desktop);
desktop_animation_start_oneshot_levelup(desktop->animation);
const Icon* icon = desktop_animation_get_oneshot_frame(desktop->animation);
desktop_main_switch_dolphin_icon(desktop->main_view, icon);
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain);
scene_manager_set_scene_state(
desktop->scene_manager, DesktopSceneLevelUp, LEVELUP_SCENE_PLAYING);
}
bool desktop_scene_levelup_on_event(void* context, SceneManagerEvent event) {
Desktop* desktop = (Desktop*)context;
bool consumed = false;
DesktopMainEvent main_event = event.event;
if(event.type == SceneManagerEventTypeCustom) {
if(main_event == DesktopMainEventUpdateOneShotAnimation) {
const Icon* icon = desktop_animation_get_oneshot_frame(desktop->animation);
if(icon) {
desktop_main_switch_dolphin_icon(desktop->main_view, icon);
} else {
scene_manager_set_scene_state(
desktop->scene_manager, DesktopSceneLevelUp, LEVELUP_SCENE_STOPPED);
}
consumed = true;
} else {
if(scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneLevelUp) ==
LEVELUP_SCENE_STOPPED) {
scene_manager_previous_scene(desktop->scene_manager);
}
}
}
return consumed;
}
void desktop_scene_levelup_on_exit(void* context) {
Desktop* desktop = (Desktop*)context;
Dolphin* dolphin = furi_record_open("dolphin");
dolphin_upgrade_level(dolphin);
furi_record_close("dolphin");
desktop_animation_set_animation_changed_callback(desktop->animation, NULL, NULL);
desktop_start_new_idle_animation(desktop->animation);
}

View File

@ -3,7 +3,7 @@
#include <toolbox/saved_struct.h>
#include <stdbool.h>
void desktop_scene_lock_menu_callback(DesktopLockMenuEvent event, void* context) {
void desktop_scene_lock_menu_callback(DesktopEvent event, void* context) {
Desktop* desktop = (Desktop*)context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
}

View File

@ -1,34 +1,28 @@
#include "../desktop_i.h"
#include "../views/desktop_locked.h"
#include "desktop/helpers/desktop_animation.h"
#include "desktop/views/desktop_main.h"
void desktop_scene_locked_callback(DesktopLockedEvent event, void* context) {
void desktop_scene_locked_callback(DesktopEvent event, void* context) {
Desktop* desktop = (Desktop*)context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
}
static void desktop_scene_locked_animation_changed_callback(void* context) {
static void desktop_scene_locked_new_idle_animation_callback(void* context) {
furi_assert(context);
Desktop* desktop = context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventUpdateAnimation);
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopLockedEventCheckAnimation);
}
void desktop_scene_locked_on_enter(void* context) {
Desktop* desktop = (Desktop*)context;
DesktopLockedView* locked_view = desktop->locked_view;
animation_manager_set_new_idle_callback(
desktop->animation_manager, desktop_scene_locked_new_idle_animation_callback);
desktop_locked_set_callback(locked_view, desktop_scene_locked_callback, desktop);
desktop_locked_reset_door_pos(locked_view);
desktop_locked_update_hint_timeout(locked_view);
desktop_animation_set_animation_changed_callback(
desktop->animation, desktop_scene_locked_animation_changed_callback, desktop);
bool status_bar_background_black = false;
const Icon* icon =
desktop_animation_get_animation(desktop->animation, &status_bar_background_black);
desktop_locked_set_dolphin_animation(locked_view, icon, status_bar_background_black);
uint32_t state = scene_manager_get_scene_state(desktop->scene_manager, DesktopViewLocked);
desktop_locked_with_pin(desktop->locked_view, state == DesktopLockedWithPin);
@ -39,7 +33,7 @@ void desktop_scene_locked_on_enter(void* context) {
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewLocked);
}
static bool desktop_scene_locked_check_pin(Desktop* desktop, DesktopMainEvent event) {
static bool desktop_scene_locked_check_pin(Desktop* desktop, DesktopEvent event) {
bool match = false;
size_t length = desktop->pincode_buffer.length;
@ -81,15 +75,10 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) {
case DesktopLockedEventInputReset:
desktop->pincode_buffer.length = 0;
break;
case DesktopMainEventUpdateAnimation: {
bool status_bar_background_black = false;
const Icon* icon =
desktop_animation_get_animation(desktop->animation, &status_bar_background_black);
desktop_locked_set_dolphin_animation(
desktop->locked_view, icon, status_bar_background_black);
case DesktopLockedEventCheckAnimation:
animation_manager_check_blocking_process(desktop->animation_manager);
consumed = true;
break;
}
default:
if(desktop_scene_locked_check_pin(desktop, event.event)) {
scene_manager_set_scene_state(
@ -106,7 +95,7 @@ bool desktop_scene_locked_on_event(void* context, SceneManagerEvent event) {
void desktop_scene_locked_on_exit(void* context) {
Desktop* desktop = (Desktop*)context;
desktop_animation_set_animation_changed_callback(desktop->animation, NULL, NULL);
animation_manager_set_new_idle_callback(desktop->animation_manager, NULL);
desktop_locked_reset_counter(desktop->locked_view);
osTimerStop(desktop->locked_view->timer);
}

View File

@ -2,14 +2,51 @@
#include "../views/desktop_main.h"
#include "applications.h"
#include "assets_icons.h"
#include "cmsis_os2.h"
#include "desktop/desktop.h"
#include "desktop/views/desktop_events.h"
#include "dolphin/dolphin.h"
#include "furi/pubsub.h"
#include "furi/record.h"
#include "storage/storage-glue.h"
#include "furi/thread.h"
#include "storage/storage_glue.h"
#include <loader/loader.h>
#include <m-list.h>
#define MAIN_VIEW_DEFAULT (0UL)
static void desktop_scene_main_app_started_callback(const void* message, void* context) {
furi_assert(context);
Desktop* desktop = context;
const LoaderEvent* event = message;
if(event->type == LoaderEventTypeApplicationStarted) {
view_dispatcher_send_custom_event(
desktop->view_dispatcher, DesktopMainEventBeforeAppStarted);
osSemaphoreAcquire(desktop->unload_animation_semaphore, osWaitForever);
} else if(event->type == LoaderEventTypeApplicationStopped) {
view_dispatcher_send_custom_event(
desktop->view_dispatcher, DesktopMainEventAfterAppFinished);
}
}
static void desktop_scene_main_new_idle_animation_callback(void* context) {
furi_assert(context);
Desktop* desktop = context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventNewIdleAnimation);
}
static void desktop_scene_main_check_animation_callback(void* context) {
furi_assert(context);
Desktop* desktop = context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventCheckAnimation);
}
static void desktop_scene_main_interact_animation_callback(void* context) {
furi_assert(context);
Desktop* desktop = context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventInteractAnimation);
}
static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* flipper_app) {
furi_assert(desktop);
furi_assert(flipper_app);
@ -28,21 +65,27 @@ static void desktop_switch_to_app(Desktop* desktop, const FlipperApplication* fl
furi_thread_start(desktop->scene_thread);
}
void desktop_scene_main_callback(DesktopMainEvent event, void* context) {
void desktop_scene_main_callback(DesktopEvent event, void* context) {
Desktop* desktop = (Desktop*)context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
}
static void desktop_scene_main_animation_changed_callback(void* context) {
furi_assert(context);
Desktop* desktop = context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopMainEventUpdateAnimation);
}
void desktop_scene_main_on_enter(void* context) {
Desktop* desktop = (Desktop*)context;
DesktopMainView* main_view = desktop->main_view;
animation_manager_set_context(desktop->animation_manager, desktop);
animation_manager_set_new_idle_callback(
desktop->animation_manager, desktop_scene_main_new_idle_animation_callback);
animation_manager_set_check_callback(
desktop->animation_manager, desktop_scene_main_check_animation_callback);
animation_manager_set_interact_callback(
desktop->animation_manager, desktop_scene_main_interact_animation_callback);
furi_assert(osSemaphoreGetCount(desktop->unload_animation_semaphore) == 0);
desktop->app_start_stop_subscription = furi_pubsub_subscribe(
loader_get_pubsub(), desktop_scene_main_app_started_callback, desktop);
desktop_main_set_callback(main_view, desktop_scene_main_callback, desktop);
view_port_enabled_set(desktop->lock_viewport, false);
@ -51,13 +94,6 @@ void desktop_scene_main_on_enter(void* context) {
desktop_main_unlocked(desktop->main_view);
}
desktop_animation_activate(desktop->animation);
desktop_animation_set_animation_changed_callback(
desktop->animation, desktop_scene_main_animation_changed_callback, desktop);
bool status_bar_background_black = false;
const Icon* icon =
desktop_animation_get_animation(desktop->animation, &status_bar_background_black);
desktop_main_switch_dolphin_animation(desktop->main_view, icon, status_bar_background_black);
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewMain);
}
@ -83,43 +119,51 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
break;
case DesktopMainEventOpenArchive:
#ifdef APP_ARCHIVE
animation_manager_unload_and_stall_animation(desktop->animation_manager);
desktop_switch_to_app(desktop, &FLIPPER_ARCHIVE);
animation_manager_load_and_continue_animation(desktop->animation_manager);
#endif
consumed = true;
break;
case DesktopMainEventOpenFavorite:
LOAD_DESKTOP_SETTINGS(&desktop->settings);
desktop_switch_to_app(desktop, &FLIPPER_APPS[desktop->settings.favorite]);
consumed = true;
break;
case DesktopMainEventUpdateAnimation: {
bool status_bar_background_black = false;
const Icon* icon =
desktop_animation_get_animation(desktop->animation, &status_bar_background_black);
desktop_main_switch_dolphin_animation(
desktop->main_view, icon, status_bar_background_black);
consumed = true;
break;
}
case DesktopMainEventRightShort: {
DesktopAnimationState state = desktop_animation_handle_right(desktop->animation);
if(state == DesktopAnimationStateLevelUpIsPending) {
scene_manager_next_scene(desktop->scene_manager, DesktopSceneLevelUp);
animation_manager_unload_and_stall_animation(desktop->animation_manager);
if(desktop->settings.favorite < FLIPPER_APPS_COUNT) {
desktop_switch_to_app(desktop, &FLIPPER_APPS[desktop->settings.favorite]);
} else {
FURI_LOG_E("DesktopSrv", "Can't find favorite application");
}
animation_manager_load_and_continue_animation(desktop->animation_manager);
consumed = true;
break;
case DesktopMainEventCheckAnimation:
animation_manager_check_blocking_process(desktop->animation_manager);
consumed = true;
break;
case DesktopMainEventNewIdleAnimation:
animation_manager_new_idle_process(desktop->animation_manager);
consumed = true;
break;
case DesktopMainEventInteractAnimation:
animation_manager_interact_process(desktop->animation_manager);
consumed = true;
break;
case DesktopMainEventBeforeAppStarted:
animation_manager_unload_and_stall_animation(desktop->animation_manager);
osSemaphoreRelease(desktop->unload_animation_semaphore);
consumed = true;
break;
case DesktopMainEventAfterAppFinished:
animation_manager_load_and_continue_animation(desktop->animation_manager);
consumed = true;
break;
}
default:
break;
}
if(event.event != DesktopMainEventUpdateAnimation) {
desktop_animation_activate(desktop->animation);
}
} else if(event.type != SceneManagerEventTypeTick) {
desktop_animation_activate(desktop->animation);
}
return consumed;
@ -128,7 +172,18 @@ bool desktop_scene_main_on_event(void* context, SceneManagerEvent event) {
void desktop_scene_main_on_exit(void* context) {
Desktop* desktop = (Desktop*)context;
desktop_animation_set_animation_changed_callback(desktop->animation, NULL, NULL);
/**
* We're allowed to leave this scene only when any other app & loader
* is finished, that's why we can be sure there is no task waiting
* for start/stop semaphore
*/
furi_pubsub_unsubscribe(loader_get_pubsub(), desktop->app_start_stop_subscription);
furi_assert(osSemaphoreGetCount(desktop->unload_animation_semaphore) == 0);
animation_manager_set_new_idle_callback(desktop->animation_manager, NULL);
animation_manager_set_check_callback(desktop->animation_manager, NULL);
animation_manager_set_interact_callback(desktop->animation_manager, NULL);
animation_manager_set_context(desktop->animation_manager, desktop);
scene_manager_set_scene_state(desktop->scene_manager, DesktopSceneMain, MAIN_VIEW_DEFAULT);
desktop_main_reset_hint(desktop->main_view);
}

View File

@ -7,17 +7,11 @@
#include <furi.h>
#include <storage/storage.h>
#include <time.h>
typedef enum {
DesktopDebugEventDeed,
DesktopDebugEventWrongDeed,
DesktopDebugEventSaveState,
DesktopDebugEventExit,
} DesktopDebugEvent;
#include "desktop_events.h"
typedef struct DesktopDebugView DesktopDebugView;
typedef void (*DesktopDebugViewCallback)(DesktopDebugEvent event, void* context);
typedef void (*DesktopDebugViewCallback)(DesktopEvent event, void* context);
// Debug info
typedef enum {
@ -51,4 +45,4 @@ DesktopDebugView* desktop_debug_alloc();
void desktop_debug_free(DesktopDebugView* debug_view);
void desktop_debug_get_dolphin_data(DesktopDebugView* debug_view);
void desktop_debug_reset_screen_idx(DesktopDebugView* debug_view);
void desktop_debug_reset_screen_idx(DesktopDebugView* debug_view);

View File

@ -0,0 +1,30 @@
#pragma once
typedef enum {
DesktopMainEventOpenLockMenu,
DesktopMainEventOpenArchive,
DesktopMainEventOpenFavorite,
DesktopMainEventOpenMenu,
DesktopMainEventOpenDebug,
DesktopMainEventUnlocked,
DesktopMainEventRightShort,
DesktopMainEventCheckAnimation,
DesktopMainEventNewIdleAnimation,
DesktopMainEventInteractAnimation,
DesktopMainEventBeforeAppStarted,
DesktopMainEventAfterAppFinished,
DesktopLockedEventUnlock,
DesktopLockedEventUpdate,
DesktopLockedEventInputReset,
DesktopLockedEventCheckAnimation,
DesktopLockedEventMax,
DesktopDebugEventDeed,
DesktopDebugEventWrongDeed,
DesktopDebugEventSaveState,
DesktopDebugEventExit,
DesktopFirstStartCompleted,
DesktopFirstStartPoweroff,
DesktopLockMenuEventLock,
DesktopLockMenuEventPinLock,
DesktopLockMenuEventExit,
} DesktopEvent;

View File

@ -5,15 +5,11 @@
#include <gui/canvas.h>
#include <gui/elements.h>
#include <furi.h>
typedef enum {
DesktopFirstStartCompleted,
DesktopFirstStartPoweroff,
} DesktopFirstStartEvent;
#include "desktop_events.h"
typedef struct DesktopFirstStartView DesktopFirstStartView;
typedef void (*DesktopFirstStartViewCallback)(DesktopFirstStartEvent event, void* context);
typedef void (*DesktopFirstStartViewCallback)(DesktopEvent event, void* context);
DesktopFirstStartView* desktop_first_start_alloc();

View File

@ -5,18 +5,13 @@
#include <gui/canvas.h>
#include <gui/elements.h>
#include <furi.h>
#include "desktop_events.h"
#define HINT_TIMEOUT 2
typedef enum {
DesktopLockMenuEventLock,
DesktopLockMenuEventPinLock,
DesktopLockMenuEventExit,
} DesktopLockMenuEvent;
typedef struct DesktopLockMenuView DesktopLockMenuView;
typedef void (*DesktopLockMenuViewCallback)(DesktopLockMenuEvent event, void* context);
typedef void (*DesktopLockMenuViewCallback)(DesktopEvent event, void* context);
struct DesktopLockMenuView {
View* view;

View File

@ -17,21 +17,6 @@ void locked_view_timer_callback(void* context) {
locked_view->callback(DesktopLockedEventUpdate, locked_view->context);
}
void desktop_locked_set_dolphin_animation(
DesktopLockedView* locked_view,
const Icon* icon,
bool status_bar_background_black) {
with_view_model(
locked_view->view, (DesktopLockedViewModel * model) {
if(model->animation) icon_animation_free(model->animation);
model->animation = icon_animation_alloc(icon);
view_tie_icon_animation(locked_view->view, model->animation);
icon_animation_start(model->animation);
model->status_bar_background_black = status_bar_background_black;
return true;
});
}
void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view) {
with_view_model(
locked_view->view, (DesktopLockedViewModel * model) {
@ -95,7 +80,6 @@ void desktop_locked_with_pin(DesktopLockedView* locked_view, bool locked) {
void desktop_locked_render(Canvas* canvas, void* model) {
DesktopLockedViewModel* m = model;
uint32_t now = osKernelGetTickCount();
canvas_clear(canvas);
canvas_set_color(canvas, ColorBlack);
if(!m->animation_seq_end) {

View File

@ -5,6 +5,7 @@
#include <gui/canvas.h>
#include <gui/elements.h>
#include <furi.h>
#include "desktop_events.h"
#define UNLOCK_RST_TIMEOUT 300
#define UNLOCK_CNT 2 // 3 actually
@ -14,12 +15,6 @@
#define DOOR_R_POS 115
#define DOOR_R_POS_MIN 60
typedef enum {
DesktopLockedEventUnlock = 10U,
DesktopLockedEventUpdate = 11U,
DesktopLockedEventInputReset = 12U,
} DesktopLockedEvent;
typedef enum {
DesktopLockedWithPin,
DesktopLockedNoPin,
@ -27,7 +22,7 @@ typedef enum {
typedef struct DesktopLockedView DesktopLockedView;
typedef void (*DesktopLockedViewCallback)(DesktopLockedEvent event, void* context);
typedef void (*DesktopLockedViewCallback)(DesktopEvent event, void* context);
struct DesktopLockedView {
View* view;
@ -57,10 +52,6 @@ void desktop_locked_set_callback(
DesktopLockedViewCallback callback,
void* context);
void desktop_locked_set_dolphin_animation(
DesktopLockedView* locked_view,
const Icon* icon,
bool status_bar_background_black);
void desktop_locked_update_hint_timeout(DesktopLockedView* locked_view);
void desktop_locked_reset_counter(DesktopLockedView* locked_view);
void desktop_locked_reset_door_pos(DesktopLockedView* locked_view);

View File

@ -1,8 +1,13 @@
#include "dolphin/dolphin.h"
#include "furi/record.h"
#include "gui/canvas.h"
#include "gui/view.h"
#include "gui/view_composed.h"
#include "input/input.h"
#include <furi.h>
#include "../desktop_i.h"
#include "desktop_main.h"
//#include "../animations/views/bubble_animation_view.h"
void desktop_main_set_callback(
DesktopMainView* main_view,
@ -49,7 +54,6 @@ void desktop_main_switch_dolphin_icon(DesktopMainView* main_view, const Icon* ic
}
void desktop_main_render(Canvas* canvas, void* model) {
canvas_clear(canvas);
DesktopMainViewModel* m = model;
uint32_t now = osKernelGetTickCount();
@ -78,6 +82,7 @@ bool desktop_main_input(InputEvent* event, void* context) {
furi_assert(context);
DesktopMainView* main_view = context;
bool consumed = false;
if(event->key == InputKeyOk && event->type == InputTypeShort) {
main_view->callback(DesktopMainEventOpenMenu, main_view->context);
@ -91,11 +96,13 @@ bool desktop_main_input(InputEvent* event, void* context) {
main_view->callback(DesktopMainEventOpenFavorite, main_view->context);
} else if(event->key == InputKeyRight && event->type == InputTypeShort) {
main_view->callback(DesktopMainEventRightShort, main_view->context);
} else if(event->key == InputKeyBack && event->type == InputTypeShort) {
consumed = true;
}
desktop_main_reset_hint(main_view);
return true;
return consumed;
}
void desktop_main_enter(void* context) {
@ -119,6 +126,7 @@ void desktop_main_exit(void* context) {
DesktopMainView* desktop_main_alloc() {
DesktopMainView* main_view = furi_alloc(sizeof(DesktopMainView));
main_view->view = view_alloc();
view_allocate_model(main_view->view, ViewModelTypeLocking, sizeof(DesktopMainViewModel));
view_set_context(main_view->view, main_view);

View File

@ -1,26 +1,16 @@
#pragma once
#include "gui/view_composed.h"
#include <gui/gui_i.h>
#include <gui/view.h>
#include <gui/canvas.h>
#include <gui/elements.h>
#include <furi.h>
typedef enum {
DesktopMainEventOpenLockMenu,
DesktopMainEventOpenArchive,
DesktopMainEventOpenFavorite,
DesktopMainEventOpenMenu,
DesktopMainEventOpenDebug,
DesktopMainEventUnlocked,
DesktopMainEventRightShort,
DesktopMainEventUpdateAnimation,
DesktopMainEventUpdateOneShotAnimation,
} DesktopMainEvent;
#include "desktop_events.h"
typedef struct DesktopMainView DesktopMainView;
typedef void (*DesktopMainViewCallback)(DesktopMainEvent event, void* context);
typedef void (*DesktopMainViewCallback)(DesktopEvent event, void* context);
struct DesktopMainView {
View* view;

View File

@ -1,7 +1,7 @@
#include "dialogs-i.h"
#include "dialogs-api-lock.h"
#include "dialogs-module-file-select.h"
#include "dialogs-module-message.h"
#include "dialogs_i.h"
#include "dialogs_api_lock.h"
#include "dialogs_module_file_select.h"
#include "dialogs_module_message.h"
static DialogsApp* dialogs_app_alloc() {
DialogsApp* app = malloc(sizeof(DialogsApp));

View File

@ -1,5 +1,5 @@
#include "dialogs-i.h"
#include "dialogs-api-lock.h"
#include "dialogs_i.h"
#include "dialogs_api_lock.h"
/****************** File select ******************/

View File

@ -1,7 +1,7 @@
#pragma once
#include "dialogs.h"
#include "dialogs-message.h"
#include "view-holder.h"
#include "dialogs_message.h"
#include "view_holder.h"
#ifdef __cplusplus
extern "C" {

View File

@ -1,7 +1,7 @@
#pragma once
#include <furi.h>
#include "dialogs-i.h"
#include "dialogs-api-lock.h"
#include "dialogs_i.h"
#include "dialogs_api_lock.h"
#ifdef __cplusplus
extern "C" {

View File

@ -1,5 +1,5 @@
#include "dialogs-i.h"
#include "dialogs-api-lock.h"
#include "dialogs_i.h"
#include "dialogs_api_lock.h"
#include <gui/modules/file_select.h>
typedef struct {

View File

@ -1,5 +1,5 @@
#pragma once
#include "dialogs-message.h"
#include "dialogs_message.h"
#ifdef __cplusplus
extern "C" {

View File

@ -1,5 +1,5 @@
#include "dialogs-i.h"
#include "dialogs-api-lock.h"
#include "dialogs_i.h"
#include "dialogs_api_lock.h"
#include <gui/modules/dialog_ex.h>
typedef struct {

View File

@ -1,5 +1,5 @@
#pragma once
#include "dialogs-message.h"
#include "dialogs_message.h"
#ifdef __cplusplus
extern "C" {

View File

@ -1,4 +1,4 @@
#include "view-holder.h"
#include "view_holder.h"
#include <gui/view_i.h>
#define TAG "ViewHolder"

View File

@ -111,13 +111,16 @@ int32_t dolphin_srv(void* p) {
furi_pubsub_publish(dolphin->pubsub, &event);
}
} else if(event.type == DolphinEventTypeStats) {
event.stats->icounter = dolphin->state->data.icounter;
event.stats->butthurt = dolphin->state->data.butthurt;
// TODO: correct icounter/butthurt changing, stub till then
event.stats->icounter = 0;
event.stats->butthurt = 0;
event.stats->timestamp = dolphin->state->data.timestamp;
event.stats->level = dolphin_get_level(dolphin->state->data.icounter);
event.stats->level_up_is_pending =
!dolphin_state_xp_to_levelup(dolphin->state->data.icounter);
event.stats->level = 1;
event.stats->level_up_is_pending = 0;
} else if(event.type == DolphinEventTypeFlush) {
// TODO: correct icounter/butthurt changing, stub till then
dolphin->state->data.butthurt = 0;
dolphin->state->data.icounter = 0;
dolphin_state_save(dolphin->state);
}
dolphin_event_release(dolphin, &event);

View File

@ -1,6 +1,7 @@
#pragma once
#include "furi/pubsub.h"
#include "gui/view.h"
#include "helpers/dolphin_deed.h"
#include <stdbool.h>

View File

@ -2,7 +2,7 @@
#include "furi/pubsub.h"
#include <furi.h>
#include <furi-hal.h>
#include <furi_hal.h>
#include "dolphin.h"
#include "helpers/dolphin_state.h"
@ -11,6 +11,9 @@ typedef enum {
DolphinEventTypeDeed,
DolphinEventTypeStats,
DolphinEventTypeFlush,
DolphinEventTypeAnimationStartNewIdle,
DolphinEventTypeAnimationCheckBlocking,
DolphinEventTypeAnimationInteract,
} DolphinEventType;
typedef struct {

View File

@ -2,7 +2,7 @@
#include <stdint.h>
#include <storage/storage.h>
#include <furi.h>
#include <furi-hal.h>
#include <furi_hal.h>
#include <math.h>
#include <toolbox/saved_struct.h>

View File

@ -5,7 +5,7 @@
#include "furi/record.h"
#include <furi.h>
#include <gui/gui.h>
#include <furi-hal-version.h>
#include <furi_hal_version.h>
#include "dolphin/dolphin.h"
#include "math.h"

Some files were not shown because too many files have changed in this diff Show More