Merge remote-tracking branch 'origin/release-candidate' into release
							
								
								
									
										20
									
								
								.vscode/example/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						@ -4,13 +4,13 @@
 | 
			
		||||
    "version": "2.0.0",
 | 
			
		||||
    "tasks": [
 | 
			
		||||
        {
 | 
			
		||||
            "label": "[Release] Build",
 | 
			
		||||
            "label": "[Release] Build Firmware",
 | 
			
		||||
            "group": "build",
 | 
			
		||||
            "type": "shell",
 | 
			
		||||
            "command": "./fbt COMPACT=1 DEBUG=0"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "label": "[Debug] Build",
 | 
			
		||||
            "label": "[Debug] Build Firmware",
 | 
			
		||||
            "group": "build",
 | 
			
		||||
            "type": "shell",
 | 
			
		||||
            "command": "./fbt"
 | 
			
		||||
@ -123,17 +123,29 @@
 | 
			
		||||
            "type": "shell",
 | 
			
		||||
            "command": "./fbt COMPACT=1 DEBUG=0 fap_dist"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "label": "[Debug] Build App",
 | 
			
		||||
            "group": "build",
 | 
			
		||||
            "type": "shell",
 | 
			
		||||
            "command": "./fbt build APPSRC=${relativeFileDirname}"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "label": "[Release] Build App",
 | 
			
		||||
            "group": "build",
 | 
			
		||||
            "type": "shell",
 | 
			
		||||
            "command": "./fbt COMPACT=1 DEBUG=0 build APPSRC=${relativeFileDirname}"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "label": "[Debug] Launch App on Flipper",
 | 
			
		||||
            "group": "build",
 | 
			
		||||
            "type": "shell",
 | 
			
		||||
            "command": "./fbt launch_app APPSRC=${relativeFileDirname}"
 | 
			
		||||
            "command": "./fbt launch APPSRC=${relativeFileDirname}"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "label": "[Release] Launch App on Flipper",
 | 
			
		||||
            "group": "build",
 | 
			
		||||
            "type": "shell",
 | 
			
		||||
            "command": "./fbt COMPACT=1 DEBUG=0 launch_app APPSRC=${relativeFileDirname}"
 | 
			
		||||
            "command": "./fbt COMPACT=1 DEBUG=0 launch APPSRC=${relativeFileDirname}"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
            "label": "[Debug] Launch App on Flipper with Serial Console",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										14
									
								
								applications/debug/subghz_test/application.fam
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,14 @@
 | 
			
		||||
App(
 | 
			
		||||
    appid="subghz_test",
 | 
			
		||||
    name="Sub-Ghz test",
 | 
			
		||||
    apptype=FlipperAppType.DEBUG,
 | 
			
		||||
    targets=["f7"],
 | 
			
		||||
    entry_point="subghz_test_app",
 | 
			
		||||
    requires=["gui"],
 | 
			
		||||
    stack_size=4 * 1024,
 | 
			
		||||
    order=50,
 | 
			
		||||
    fap_icon="subghz_test_10px.png",
 | 
			
		||||
    fap_category="Debug",
 | 
			
		||||
    fap_icon_assets="images",
 | 
			
		||||
    fap_version="0.1",
 | 
			
		||||
)
 | 
			
		||||
@ -0,0 +1,7 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    //SubGhzTestCustomEvent
 | 
			
		||||
    SubGhzTestCustomEventStartId = 100,
 | 
			
		||||
    SubGhzTestCustomEventSceneShowOnlyRX,
 | 
			
		||||
} SubGhzTestCustomEvent;
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
#include "subghz_testing.h"
 | 
			
		||||
#include "subghz_test_frequency.h"
 | 
			
		||||
 | 
			
		||||
const uint32_t subghz_frequencies_testing[] = {
 | 
			
		||||
    /* 300 - 348 */
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "../subghz_i.h"
 | 
			
		||||
#include "../subghz_test_app_i.h"
 | 
			
		||||
 | 
			
		||||
extern const uint32_t subghz_frequencies_testing[];
 | 
			
		||||
extern const uint32_t subghz_frequencies_count_testing;
 | 
			
		||||
							
								
								
									
										18
									
								
								applications/debug/subghz_test/helpers/subghz_test_types.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,18 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <furi.h>
 | 
			
		||||
#include <furi_hal.h>
 | 
			
		||||
 | 
			
		||||
#define SUBGHZ_TEST_VERSION_APP "0.1"
 | 
			
		||||
#define SUBGHZ_TEST_DEVELOPED "SkorP"
 | 
			
		||||
#define SUBGHZ_TEST_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    SubGhzTestViewVariableItemList,
 | 
			
		||||
    SubGhzTestViewSubmenu,
 | 
			
		||||
    SubGhzTestViewStatic,
 | 
			
		||||
    SubGhzTestViewCarrier,
 | 
			
		||||
    SubGhzTestViewPacket,
 | 
			
		||||
    SubGhzTestViewWidget,
 | 
			
		||||
    SubGhzTestViewPopup,
 | 
			
		||||
} SubGhzTestView;
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								applications/debug/subghz_test/images/DolphinCommon_56x48.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.4 KiB  | 
							
								
								
									
										244
									
								
								applications/debug/subghz_test/protocol/math.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,244 @@
 | 
			
		||||
#include "math.h"
 | 
			
		||||
 | 
			
		||||
uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t bit_count) {
 | 
			
		||||
    uint64_t reverse_key = 0;
 | 
			
		||||
    for(uint8_t i = 0; i < bit_count; i++) {
 | 
			
		||||
        reverse_key = reverse_key << 1 | bit_read(key, i);
 | 
			
		||||
    }
 | 
			
		||||
    return reverse_key;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t bit_count) {
 | 
			
		||||
    uint8_t parity = 0;
 | 
			
		||||
    for(uint8_t i = 0; i < bit_count; i++) {
 | 
			
		||||
        parity += bit_read(key, i);
 | 
			
		||||
    }
 | 
			
		||||
    return parity & 0x01;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t subghz_protocol_blocks_crc4(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint8_t polynomial,
 | 
			
		||||
    uint8_t init) {
 | 
			
		||||
    uint8_t remainder = init << 4; // LSBs are unused
 | 
			
		||||
    uint8_t poly = polynomial << 4;
 | 
			
		||||
    uint8_t bit;
 | 
			
		||||
 | 
			
		||||
    while(size--) {
 | 
			
		||||
        remainder ^= *message++;
 | 
			
		||||
        for(bit = 0; bit < 8; bit++) {
 | 
			
		||||
            if(remainder & 0x80) {
 | 
			
		||||
                remainder = (remainder << 1) ^ poly;
 | 
			
		||||
            } else {
 | 
			
		||||
                remainder = (remainder << 1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return remainder >> 4 & 0x0f; // discard the LSBs
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t subghz_protocol_blocks_crc7(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint8_t polynomial,
 | 
			
		||||
    uint8_t init) {
 | 
			
		||||
    uint8_t remainder = init << 1; // LSB is unused
 | 
			
		||||
    uint8_t poly = polynomial << 1;
 | 
			
		||||
 | 
			
		||||
    for(size_t byte = 0; byte < size; ++byte) {
 | 
			
		||||
        remainder ^= message[byte];
 | 
			
		||||
        for(uint8_t bit = 0; bit < 8; ++bit) {
 | 
			
		||||
            if(remainder & 0x80) {
 | 
			
		||||
                remainder = (remainder << 1) ^ poly;
 | 
			
		||||
            } else {
 | 
			
		||||
                remainder = (remainder << 1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return remainder >> 1 & 0x7f; // discard the LSB
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t subghz_protocol_blocks_crc8(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint8_t polynomial,
 | 
			
		||||
    uint8_t init) {
 | 
			
		||||
    uint8_t remainder = init;
 | 
			
		||||
 | 
			
		||||
    for(size_t byte = 0; byte < size; ++byte) {
 | 
			
		||||
        remainder ^= message[byte];
 | 
			
		||||
        for(uint8_t bit = 0; bit < 8; ++bit) {
 | 
			
		||||
            if(remainder & 0x80) {
 | 
			
		||||
                remainder = (remainder << 1) ^ polynomial;
 | 
			
		||||
            } else {
 | 
			
		||||
                remainder = (remainder << 1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return remainder;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t subghz_protocol_blocks_crc8le(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint8_t polynomial,
 | 
			
		||||
    uint8_t init) {
 | 
			
		||||
    uint8_t remainder = subghz_protocol_blocks_reverse_key(init, 8);
 | 
			
		||||
    polynomial = subghz_protocol_blocks_reverse_key(polynomial, 8);
 | 
			
		||||
 | 
			
		||||
    for(size_t byte = 0; byte < size; ++byte) {
 | 
			
		||||
        remainder ^= message[byte];
 | 
			
		||||
        for(uint8_t bit = 0; bit < 8; ++bit) {
 | 
			
		||||
            if(remainder & 1) {
 | 
			
		||||
                remainder = (remainder >> 1) ^ polynomial;
 | 
			
		||||
            } else {
 | 
			
		||||
                remainder = (remainder >> 1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return remainder;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint16_t subghz_protocol_blocks_crc16lsb(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint16_t polynomial,
 | 
			
		||||
    uint16_t init) {
 | 
			
		||||
    uint16_t remainder = init;
 | 
			
		||||
 | 
			
		||||
    for(size_t byte = 0; byte < size; ++byte) {
 | 
			
		||||
        remainder ^= message[byte];
 | 
			
		||||
        for(uint8_t bit = 0; bit < 8; ++bit) {
 | 
			
		||||
            if(remainder & 1) {
 | 
			
		||||
                remainder = (remainder >> 1) ^ polynomial;
 | 
			
		||||
            } else {
 | 
			
		||||
                remainder = (remainder >> 1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return remainder;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint16_t subghz_protocol_blocks_crc16(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint16_t polynomial,
 | 
			
		||||
    uint16_t init) {
 | 
			
		||||
    uint16_t remainder = init;
 | 
			
		||||
 | 
			
		||||
    for(size_t byte = 0; byte < size; ++byte) {
 | 
			
		||||
        remainder ^= message[byte] << 8;
 | 
			
		||||
        for(uint8_t bit = 0; bit < 8; ++bit) {
 | 
			
		||||
            if(remainder & 0x8000) {
 | 
			
		||||
                remainder = (remainder << 1) ^ polynomial;
 | 
			
		||||
            } else {
 | 
			
		||||
                remainder = (remainder << 1);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return remainder;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t subghz_protocol_blocks_lfsr_digest8(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint8_t gen,
 | 
			
		||||
    uint8_t key) {
 | 
			
		||||
    uint8_t sum = 0;
 | 
			
		||||
    for(size_t byte = 0; byte < size; ++byte) {
 | 
			
		||||
        uint8_t data = message[byte];
 | 
			
		||||
        for(int i = 7; i >= 0; --i) {
 | 
			
		||||
            // XOR key into sum if data bit is set
 | 
			
		||||
            if((data >> i) & 1) sum ^= key;
 | 
			
		||||
 | 
			
		||||
            // roll the key right (actually the LSB is dropped here)
 | 
			
		||||
            // and apply the gen (needs to include the dropped LSB as MSB)
 | 
			
		||||
            if(key & 1)
 | 
			
		||||
                key = (key >> 1) ^ gen;
 | 
			
		||||
            else
 | 
			
		||||
                key = (key >> 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return sum;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t subghz_protocol_blocks_lfsr_digest8_reflect(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint8_t gen,
 | 
			
		||||
    uint8_t key) {
 | 
			
		||||
    uint8_t sum = 0;
 | 
			
		||||
    // Process message from last byte to first byte (reflected)
 | 
			
		||||
    for(int byte = size - 1; byte >= 0; --byte) {
 | 
			
		||||
        uint8_t data = message[byte];
 | 
			
		||||
        // Process individual bits of each byte (reflected)
 | 
			
		||||
        for(uint8_t i = 0; i < 8; ++i) {
 | 
			
		||||
            // XOR key into sum if data bit is set
 | 
			
		||||
            if((data >> i) & 1) {
 | 
			
		||||
                sum ^= key;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // roll the key left (actually the LSB is dropped here)
 | 
			
		||||
            // and apply the gen (needs to include the dropped lsb as MSB)
 | 
			
		||||
            if(key & 0x80)
 | 
			
		||||
                key = (key << 1) ^ gen;
 | 
			
		||||
            else
 | 
			
		||||
                key = (key << 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return sum;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint16_t subghz_protocol_blocks_lfsr_digest16(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint16_t gen,
 | 
			
		||||
    uint16_t key) {
 | 
			
		||||
    uint16_t sum = 0;
 | 
			
		||||
    for(size_t byte = 0; byte < size; ++byte) {
 | 
			
		||||
        uint8_t data = message[byte];
 | 
			
		||||
        for(int8_t i = 7; i >= 0; --i) {
 | 
			
		||||
            // if data bit is set then xor with key
 | 
			
		||||
            if((data >> i) & 1) sum ^= key;
 | 
			
		||||
 | 
			
		||||
            // roll the key right (actually the LSB is dropped here)
 | 
			
		||||
            // and apply the gen (needs to include the dropped LSB as MSB)
 | 
			
		||||
            if(key & 1)
 | 
			
		||||
                key = (key >> 1) ^ gen;
 | 
			
		||||
            else
 | 
			
		||||
                key = (key >> 1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return sum;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t size) {
 | 
			
		||||
    uint32_t result = 0;
 | 
			
		||||
    for(size_t i = 0; i < size; ++i) {
 | 
			
		||||
        result += message[i];
 | 
			
		||||
    }
 | 
			
		||||
    return (uint8_t)result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t subghz_protocol_blocks_parity8(uint8_t byte) {
 | 
			
		||||
    byte ^= byte >> 4;
 | 
			
		||||
    byte &= 0xf;
 | 
			
		||||
    return (0x6996 >> byte) & 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t size) {
 | 
			
		||||
    uint8_t result = 0;
 | 
			
		||||
    for(size_t i = 0; i < size; ++i) {
 | 
			
		||||
        result ^= subghz_protocol_blocks_parity8(message[i]);
 | 
			
		||||
    }
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
uint8_t subghz_protocol_blocks_xor_bytes(uint8_t const message[], size_t size) {
 | 
			
		||||
    uint8_t result = 0;
 | 
			
		||||
    for(size_t i = 0; i < size; ++i) {
 | 
			
		||||
        result ^= message[i];
 | 
			
		||||
    }
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										222
									
								
								applications/debug/subghz_test/protocol/math.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,222 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <stdbool.h>
 | 
			
		||||
#include <stdint.h>
 | 
			
		||||
#include <stddef.h>
 | 
			
		||||
 | 
			
		||||
#define bit_read(value, bit) (((value) >> (bit)) & 0x01)
 | 
			
		||||
#define bit_set(value, bit)           \
 | 
			
		||||
    ({                                \
 | 
			
		||||
        __typeof__(value) _one = (1); \
 | 
			
		||||
        (value) |= (_one << (bit));   \
 | 
			
		||||
    })
 | 
			
		||||
#define bit_clear(value, bit)         \
 | 
			
		||||
    ({                                \
 | 
			
		||||
        __typeof__(value) _one = (1); \
 | 
			
		||||
        (value) &= ~(_one << (bit));  \
 | 
			
		||||
    })
 | 
			
		||||
#define bit_write(value, bit, bitvalue) (bitvalue ? bit_set(value, bit) : bit_clear(value, bit))
 | 
			
		||||
#define DURATION_DIFF(x, y) (((x) < (y)) ? ((y) - (x)) : ((x) - (y)))
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
extern "C" {
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/** Flip the data bitwise
 | 
			
		||||
 *
 | 
			
		||||
 * @param      key        In data
 | 
			
		||||
 * @param      bit_count  number of data bits
 | 
			
		||||
 *
 | 
			
		||||
 * @return     Reverse data
 | 
			
		||||
 */
 | 
			
		||||
uint64_t subghz_protocol_blocks_reverse_key(uint64_t key, uint8_t bit_count);
 | 
			
		||||
 | 
			
		||||
/** Get parity the data bitwise
 | 
			
		||||
 *
 | 
			
		||||
 * @param      key        In data
 | 
			
		||||
 * @param      bit_count  number of data bits
 | 
			
		||||
 *
 | 
			
		||||
 * @return     parity
 | 
			
		||||
 */
 | 
			
		||||
uint8_t subghz_protocol_blocks_get_parity(uint64_t key, uint8_t bit_count);
 | 
			
		||||
 | 
			
		||||
/** CRC-4
 | 
			
		||||
 *
 | 
			
		||||
 * @param      message     array of bytes to check
 | 
			
		||||
 * @param      size        number of bytes in message
 | 
			
		||||
 * @param      polynomial  CRC polynomial
 | 
			
		||||
 * @param      init        starting crc value
 | 
			
		||||
 *
 | 
			
		||||
 * @return     CRC value
 | 
			
		||||
 */
 | 
			
		||||
uint8_t subghz_protocol_blocks_crc4(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint8_t polynomial,
 | 
			
		||||
    uint8_t init);
 | 
			
		||||
 | 
			
		||||
/** CRC-7
 | 
			
		||||
 *
 | 
			
		||||
 * @param      message     array of bytes to check
 | 
			
		||||
 * @param      size        number of bytes in message
 | 
			
		||||
 * @param      polynomial  CRC polynomial
 | 
			
		||||
 * @param      init        starting crc value
 | 
			
		||||
 *
 | 
			
		||||
 * @return     CRC value
 | 
			
		||||
 */
 | 
			
		||||
uint8_t subghz_protocol_blocks_crc7(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint8_t polynomial,
 | 
			
		||||
    uint8_t init);
 | 
			
		||||
 | 
			
		||||
/** Generic Cyclic Redundancy Check CRC-8. Example polynomial: 0x31 = x8 + x5 +
 | 
			
		||||
 * x4 + 1 (x8 is implicit) Example polynomial: 0x80 = x8 + x7 (a normal
 | 
			
		||||
 * bit-by-bit parity XOR)
 | 
			
		||||
 *
 | 
			
		||||
 * @param      message     array of bytes to check
 | 
			
		||||
 * @param      size        number of bytes in message
 | 
			
		||||
 * @param      polynomial  byte is from x^7 to x^0 (x^8 is implicitly one)
 | 
			
		||||
 * @param      init        starting crc value
 | 
			
		||||
 *
 | 
			
		||||
 * @return     CRC value
 | 
			
		||||
 */
 | 
			
		||||
uint8_t subghz_protocol_blocks_crc8(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint8_t polynomial,
 | 
			
		||||
    uint8_t init);
 | 
			
		||||
 | 
			
		||||
/** "Little-endian" Cyclic Redundancy Check CRC-8 LE Input and output are
 | 
			
		||||
 * reflected, i.e. least significant bit is shifted in first
 | 
			
		||||
 *
 | 
			
		||||
 * @param      message     array of bytes to check
 | 
			
		||||
 * @param      size        number of bytes in message
 | 
			
		||||
 * @param      polynomial  CRC polynomial
 | 
			
		||||
 * @param      init        starting crc value
 | 
			
		||||
 *
 | 
			
		||||
 * @return     CRC value
 | 
			
		||||
 */
 | 
			
		||||
uint8_t subghz_protocol_blocks_crc8le(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint8_t polynomial,
 | 
			
		||||
    uint8_t init);
 | 
			
		||||
 | 
			
		||||
/** CRC-16 LSB. Input and output are reflected, i.e. least significant bit is
 | 
			
		||||
 * shifted in first. Note that poly and init already need to be reflected
 | 
			
		||||
 *
 | 
			
		||||
 * @param      message     array of bytes to check
 | 
			
		||||
 * @param      size        number of bytes in message
 | 
			
		||||
 * @param      polynomial  CRC polynomial
 | 
			
		||||
 * @param      init        starting crc value
 | 
			
		||||
 *
 | 
			
		||||
 * @return     CRC value
 | 
			
		||||
 */
 | 
			
		||||
uint16_t subghz_protocol_blocks_crc16lsb(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint16_t polynomial,
 | 
			
		||||
    uint16_t init);
 | 
			
		||||
 | 
			
		||||
/** CRC-16
 | 
			
		||||
 *
 | 
			
		||||
 * @param      message     array of bytes to check
 | 
			
		||||
 * @param      size        number of bytes in message
 | 
			
		||||
 * @param      polynomial  CRC polynomial
 | 
			
		||||
 * @param      init        starting crc value
 | 
			
		||||
 *
 | 
			
		||||
 * @return     CRC value
 | 
			
		||||
 */
 | 
			
		||||
uint16_t subghz_protocol_blocks_crc16(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint16_t polynomial,
 | 
			
		||||
    uint16_t init);
 | 
			
		||||
 | 
			
		||||
/** Digest-8 by "LFSR-based Toeplitz hash"
 | 
			
		||||
 *
 | 
			
		||||
 * @param      message  bytes of message data
 | 
			
		||||
 * @param      size     number of bytes to digest
 | 
			
		||||
 * @param      gen      key stream generator, needs to includes the MSB if the
 | 
			
		||||
 *                      LFSR is rolling
 | 
			
		||||
 * @param      key      initial key
 | 
			
		||||
 *
 | 
			
		||||
 * @return     digest value
 | 
			
		||||
 */
 | 
			
		||||
uint8_t subghz_protocol_blocks_lfsr_digest8(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint8_t gen,
 | 
			
		||||
    uint8_t key);
 | 
			
		||||
 | 
			
		||||
/** Digest-8 by "LFSR-based Toeplitz hash", byte reflect, bit reflect
 | 
			
		||||
 *
 | 
			
		||||
 * @param      message  bytes of message data
 | 
			
		||||
 * @param      size     number of bytes to digest
 | 
			
		||||
 * @param      gen      key stream generator, needs to includes the MSB if the
 | 
			
		||||
 *                      LFSR is rolling
 | 
			
		||||
 * @param      key      initial key
 | 
			
		||||
 *
 | 
			
		||||
 * @return     digest value
 | 
			
		||||
 */
 | 
			
		||||
uint8_t subghz_protocol_blocks_lfsr_digest8_reflect(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint8_t gen,
 | 
			
		||||
    uint8_t key);
 | 
			
		||||
 | 
			
		||||
/** Digest-16 by "LFSR-based Toeplitz hash"
 | 
			
		||||
 *
 | 
			
		||||
 * @param      message  bytes of message data
 | 
			
		||||
 * @param      size     number of bytes to digest
 | 
			
		||||
 * @param      gen      key stream generator, needs to includes the MSB if the
 | 
			
		||||
 *                      LFSR is rolling
 | 
			
		||||
 * @param      key      initial key
 | 
			
		||||
 *
 | 
			
		||||
 * @return     digest value
 | 
			
		||||
 */
 | 
			
		||||
uint16_t subghz_protocol_blocks_lfsr_digest16(
 | 
			
		||||
    uint8_t const message[],
 | 
			
		||||
    size_t size,
 | 
			
		||||
    uint16_t gen,
 | 
			
		||||
    uint16_t key);
 | 
			
		||||
 | 
			
		||||
/** Compute Addition of a number of bytes
 | 
			
		||||
 *
 | 
			
		||||
 * @param      message  bytes of message data
 | 
			
		||||
 * @param      size     number of bytes to sum
 | 
			
		||||
 *
 | 
			
		||||
 * @return     summation value
 | 
			
		||||
 */
 | 
			
		||||
uint8_t subghz_protocol_blocks_add_bytes(uint8_t const message[], size_t size);
 | 
			
		||||
 | 
			
		||||
/** Compute bit parity of a single byte (8 bits)
 | 
			
		||||
 *
 | 
			
		||||
 * @param      byte  single byte to check
 | 
			
		||||
 *
 | 
			
		||||
 * @return     1 odd parity, 0 even parity
 | 
			
		||||
 */
 | 
			
		||||
uint8_t subghz_protocol_blocks_parity8(uint8_t byte);
 | 
			
		||||
 | 
			
		||||
/** Compute bit parity of a number of bytes
 | 
			
		||||
 *
 | 
			
		||||
 * @param      message  bytes of message data
 | 
			
		||||
 * @param      size     number of bytes to sum
 | 
			
		||||
 *
 | 
			
		||||
 * @return     1 odd parity, 0 even parity
 | 
			
		||||
 */
 | 
			
		||||
uint8_t subghz_protocol_blocks_parity_bytes(uint8_t const message[], size_t size);
 | 
			
		||||
 | 
			
		||||
/** Compute XOR (byte-wide parity) of a number of bytes
 | 
			
		||||
 *
 | 
			
		||||
 * @param      message  bytes of message data
 | 
			
		||||
 * @param      size     number of bytes to sum
 | 
			
		||||
 *
 | 
			
		||||
 * @return     summation value, per bit-position 1 odd parity, 0 even parity
 | 
			
		||||
 */
 | 
			
		||||
uint8_t subghz_protocol_blocks_xor_bytes(uint8_t const message[], size_t size);
 | 
			
		||||
 | 
			
		||||
#ifdef __cplusplus
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
#include "princeton_for_testing.h"
 | 
			
		||||
 | 
			
		||||
#include <furi_hal.h>
 | 
			
		||||
#include "../blocks/math.h"
 | 
			
		||||
#include "math.h"
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Help
 | 
			
		||||
@ -1,6 +1,8 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "base.h"
 | 
			
		||||
//#include "base.h"
 | 
			
		||||
#include <furi.h>
 | 
			
		||||
#include <lib/toolbox/level_duration.h>
 | 
			
		||||
 | 
			
		||||
/** SubGhzDecoderPrinceton anonymous type */
 | 
			
		||||
typedef struct SubGhzDecoderPrinceton SubGhzDecoderPrinceton;
 | 
			
		||||
							
								
								
									
										30
									
								
								applications/debug/subghz_test/scenes/subghz_test_scene.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,30 @@
 | 
			
		||||
#include "../subghz_test_app_i.h"
 | 
			
		||||
 | 
			
		||||
// Generate scene on_enter handlers array
 | 
			
		||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
 | 
			
		||||
void (*const subghz_test_scene_on_enter_handlers[])(void*) = {
 | 
			
		||||
#include "subghz_test_scene_config.h"
 | 
			
		||||
};
 | 
			
		||||
#undef ADD_SCENE
 | 
			
		||||
 | 
			
		||||
// Generate scene on_event handlers array
 | 
			
		||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
 | 
			
		||||
bool (*const subghz_test_scene_on_event_handlers[])(void* context, SceneManagerEvent event) = {
 | 
			
		||||
#include "subghz_test_scene_config.h"
 | 
			
		||||
};
 | 
			
		||||
#undef ADD_SCENE
 | 
			
		||||
 | 
			
		||||
// Generate scene on_exit handlers array
 | 
			
		||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
 | 
			
		||||
void (*const subghz_test_scene_on_exit_handlers[])(void* context) = {
 | 
			
		||||
#include "subghz_test_scene_config.h"
 | 
			
		||||
};
 | 
			
		||||
#undef ADD_SCENE
 | 
			
		||||
 | 
			
		||||
// Initialize scene handlers configuration structure
 | 
			
		||||
const SceneManagerHandlers subghz_test_scene_handlers = {
 | 
			
		||||
    .on_enter_handlers = subghz_test_scene_on_enter_handlers,
 | 
			
		||||
    .on_event_handlers = subghz_test_scene_on_event_handlers,
 | 
			
		||||
    .on_exit_handlers = subghz_test_scene_on_exit_handlers,
 | 
			
		||||
    .scene_num = SubGhzTestSceneNum,
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										29
									
								
								applications/debug/subghz_test/scenes/subghz_test_scene.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,29 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include <gui/scene_manager.h>
 | 
			
		||||
 | 
			
		||||
// Generate scene id and total number
 | 
			
		||||
#define ADD_SCENE(prefix, name, id) SubGhzTestScene##id,
 | 
			
		||||
typedef enum {
 | 
			
		||||
#include "subghz_test_scene_config.h"
 | 
			
		||||
    SubGhzTestSceneNum,
 | 
			
		||||
} SubGhzTestScene;
 | 
			
		||||
#undef ADD_SCENE
 | 
			
		||||
 | 
			
		||||
extern const SceneManagerHandlers subghz_test_scene_handlers;
 | 
			
		||||
 | 
			
		||||
// Generate scene on_enter handlers declaration
 | 
			
		||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
 | 
			
		||||
#include "subghz_test_scene_config.h"
 | 
			
		||||
#undef ADD_SCENE
 | 
			
		||||
 | 
			
		||||
// Generate scene on_event handlers declaration
 | 
			
		||||
#define ADD_SCENE(prefix, name, id) \
 | 
			
		||||
    bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
 | 
			
		||||
#include "subghz_test_scene_config.h"
 | 
			
		||||
#undef ADD_SCENE
 | 
			
		||||
 | 
			
		||||
// Generate scene on_exit handlers declaration
 | 
			
		||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
 | 
			
		||||
#include "subghz_test_scene_config.h"
 | 
			
		||||
#undef ADD_SCENE
 | 
			
		||||
@ -0,0 +1,66 @@
 | 
			
		||||
#include "../subghz_test_app_i.h"
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_about_widget_callback(GuiButtonType result, InputType type, void* context) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    if(type == InputTypeShort) {
 | 
			
		||||
        view_dispatcher_send_custom_event(app->view_dispatcher, result);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_about_on_enter(void* context) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
 | 
			
		||||
    FuriString* temp_str;
 | 
			
		||||
    temp_str = furi_string_alloc();
 | 
			
		||||
    furi_string_printf(temp_str, "\e#%s\n", "Information");
 | 
			
		||||
 | 
			
		||||
    furi_string_cat_printf(temp_str, "Version: %s\n", SUBGHZ_TEST_VERSION_APP);
 | 
			
		||||
    furi_string_cat_printf(temp_str, "Developed by: %s\n", SUBGHZ_TEST_DEVELOPED);
 | 
			
		||||
    furi_string_cat_printf(temp_str, "Github: %s\n\n", SUBGHZ_TEST_GITHUB);
 | 
			
		||||
 | 
			
		||||
    furi_string_cat_printf(temp_str, "\e#%s\n", "Description");
 | 
			
		||||
    furi_string_cat_printf(
 | 
			
		||||
        temp_str,
 | 
			
		||||
        "This application is designed\nto test the functionality of the\nbuilt-in CC1101 module.\n\n");
 | 
			
		||||
 | 
			
		||||
    widget_add_text_box_element(
 | 
			
		||||
        app->widget,
 | 
			
		||||
        0,
 | 
			
		||||
        0,
 | 
			
		||||
        128,
 | 
			
		||||
        14,
 | 
			
		||||
        AlignCenter,
 | 
			
		||||
        AlignBottom,
 | 
			
		||||
        "\e#\e!                                                      \e!\n",
 | 
			
		||||
        false);
 | 
			
		||||
    widget_add_text_box_element(
 | 
			
		||||
        app->widget,
 | 
			
		||||
        0,
 | 
			
		||||
        2,
 | 
			
		||||
        128,
 | 
			
		||||
        14,
 | 
			
		||||
        AlignCenter,
 | 
			
		||||
        AlignBottom,
 | 
			
		||||
        "\e#\e!         Sub-Ghz Test          \e!\n",
 | 
			
		||||
        false);
 | 
			
		||||
    widget_add_text_scroll_element(app->widget, 0, 16, 128, 50, furi_string_get_cstr(temp_str));
 | 
			
		||||
    furi_string_free(temp_str);
 | 
			
		||||
 | 
			
		||||
    view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewWidget);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool subghz_test_scene_about_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    bool consumed = false;
 | 
			
		||||
    UNUSED(app);
 | 
			
		||||
    UNUSED(event);
 | 
			
		||||
 | 
			
		||||
    return consumed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_about_on_exit(void* context) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
 | 
			
		||||
    // Clear views
 | 
			
		||||
    widget_reset(app->widget);
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,29 @@
 | 
			
		||||
#include "../subghz_test_app_i.h"
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_carrier_callback(SubGhzTestCarrierEvent event, void* context) {
 | 
			
		||||
    furi_assert(context);
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    view_dispatcher_send_custom_event(app->view_dispatcher, event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_carrier_on_enter(void* context) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    subghz_test_carrier_set_callback(
 | 
			
		||||
        app->subghz_test_carrier, subghz_test_scene_carrier_callback, app);
 | 
			
		||||
    view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewCarrier);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool subghz_test_scene_carrier_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SubGhzTestCarrierEventOnlyRx) {
 | 
			
		||||
            scene_manager_next_scene(app->scene_manager, SubGhzTestSceneShowOnlyRx);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_carrier_on_exit(void* context) {
 | 
			
		||||
    UNUSED(context);
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
ADD_SCENE(subghz_test, start, Start)
 | 
			
		||||
ADD_SCENE(subghz_test, about, About)
 | 
			
		||||
ADD_SCENE(subghz_test, carrier, Carrier)
 | 
			
		||||
ADD_SCENE(subghz_test, packet, Packet)
 | 
			
		||||
ADD_SCENE(subghz_test, static, Static)
 | 
			
		||||
ADD_SCENE(subghz_test, show_only_rx, ShowOnlyRx)
 | 
			
		||||
@ -0,0 +1,29 @@
 | 
			
		||||
#include "../subghz_test_app_i.h"
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_packet_callback(SubGhzTestPacketEvent event, void* context) {
 | 
			
		||||
    furi_assert(context);
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    view_dispatcher_send_custom_event(app->view_dispatcher, event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_packet_on_enter(void* context) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    subghz_test_packet_set_callback(
 | 
			
		||||
        app->subghz_test_packet, subghz_test_scene_packet_callback, app);
 | 
			
		||||
    view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewPacket);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool subghz_test_scene_packet_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SubGhzTestPacketEventOnlyRx) {
 | 
			
		||||
            scene_manager_next_scene(app->scene_manager, SubGhzTestSceneShowOnlyRx);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_packet_on_exit(void* context) {
 | 
			
		||||
    UNUSED(context);
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,49 @@
 | 
			
		||||
#include "../subghz_test_app_i.h"
 | 
			
		||||
#include <subghz_test_icons.h>
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_show_only_rx_popup_callback(void* context) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    view_dispatcher_send_custom_event(app->view_dispatcher, SubGhzTestCustomEventSceneShowOnlyRX);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_show_only_rx_on_enter(void* context) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
 | 
			
		||||
    // Setup view
 | 
			
		||||
    Popup* popup = app->popup;
 | 
			
		||||
 | 
			
		||||
    const char* header_text = "Transmission is blocked";
 | 
			
		||||
    const char* message_text = "Transmission on\nthis frequency is\nrestricted in\nyour region";
 | 
			
		||||
    if(!furi_hal_region_is_provisioned()) {
 | 
			
		||||
        header_text = "Firmware update needed";
 | 
			
		||||
        message_text = "Please update\nfirmware before\nusing this feature\nflipp.dev/upd";
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    popup_set_header(popup, header_text, 63, 3, AlignCenter, AlignTop);
 | 
			
		||||
    popup_set_text(popup, message_text, 0, 17, AlignLeft, AlignTop);
 | 
			
		||||
    popup_set_icon(popup, 72, 17, &I_DolphinCommon_56x48);
 | 
			
		||||
 | 
			
		||||
    popup_set_timeout(popup, 1500);
 | 
			
		||||
    popup_set_context(popup, app);
 | 
			
		||||
    popup_set_callback(popup, subghz_test_scene_show_only_rx_popup_callback);
 | 
			
		||||
    popup_enable_timeout(popup);
 | 
			
		||||
    view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewPopup);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool subghz_test_scene_show_only_rx_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SubGhzTestCustomEventSceneShowOnlyRX) {
 | 
			
		||||
            scene_manager_previous_scene(app->scene_manager);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_show_only_rx_on_exit(void* context) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    Popup* popup = app->popup;
 | 
			
		||||
 | 
			
		||||
    popup_reset(popup);
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,77 @@
 | 
			
		||||
#include "../subghz_test_app_i.h"
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    SubmenuIndexSubGhzTestCarrier,
 | 
			
		||||
    SubmenuIndexSubGhzTestPacket,
 | 
			
		||||
    SubmenuIndexSubGhzTestStatic,
 | 
			
		||||
    SubmenuIndexSubGhzTestAbout,
 | 
			
		||||
} SubmenuIndex;
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_start_submenu_callback(void* context, uint32_t index) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    view_dispatcher_send_custom_event(app->view_dispatcher, index);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_start_on_enter(void* context) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    Submenu* submenu = app->submenu;
 | 
			
		||||
 | 
			
		||||
    submenu_add_item(
 | 
			
		||||
        submenu,
 | 
			
		||||
        "Carrier",
 | 
			
		||||
        SubmenuIndexSubGhzTestCarrier,
 | 
			
		||||
        subghz_test_scene_start_submenu_callback,
 | 
			
		||||
        app);
 | 
			
		||||
    submenu_add_item(
 | 
			
		||||
        submenu,
 | 
			
		||||
        "Packet",
 | 
			
		||||
        SubmenuIndexSubGhzTestPacket,
 | 
			
		||||
        subghz_test_scene_start_submenu_callback,
 | 
			
		||||
        app);
 | 
			
		||||
    submenu_add_item(
 | 
			
		||||
        submenu,
 | 
			
		||||
        "Static",
 | 
			
		||||
        SubmenuIndexSubGhzTestStatic,
 | 
			
		||||
        subghz_test_scene_start_submenu_callback,
 | 
			
		||||
        app);
 | 
			
		||||
    submenu_add_item(
 | 
			
		||||
        submenu,
 | 
			
		||||
        "About",
 | 
			
		||||
        SubmenuIndexSubGhzTestAbout,
 | 
			
		||||
        subghz_test_scene_start_submenu_callback,
 | 
			
		||||
        app);
 | 
			
		||||
 | 
			
		||||
    submenu_set_selected_item(
 | 
			
		||||
        submenu, scene_manager_get_scene_state(app->scene_manager, SubGhzTestSceneStart));
 | 
			
		||||
 | 
			
		||||
    view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewSubmenu);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool subghz_test_scene_start_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    bool consumed = false;
 | 
			
		||||
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SubmenuIndexSubGhzTestAbout) {
 | 
			
		||||
            scene_manager_next_scene(app->scene_manager, SubGhzTestSceneAbout);
 | 
			
		||||
            consumed = true;
 | 
			
		||||
        } else if(event.event == SubmenuIndexSubGhzTestCarrier) {
 | 
			
		||||
            scene_manager_next_scene(app->scene_manager, SubGhzTestSceneCarrier);
 | 
			
		||||
            consumed = true;
 | 
			
		||||
        } else if(event.event == SubmenuIndexSubGhzTestPacket) {
 | 
			
		||||
            scene_manager_next_scene(app->scene_manager, SubGhzTestScenePacket);
 | 
			
		||||
            consumed = true;
 | 
			
		||||
        } else if(event.event == SubmenuIndexSubGhzTestStatic) {
 | 
			
		||||
            scene_manager_next_scene(app->scene_manager, SubGhzTestSceneStatic);
 | 
			
		||||
            consumed = true;
 | 
			
		||||
        }
 | 
			
		||||
        scene_manager_set_scene_state(app->scene_manager, SubGhzTestSceneStart, event.event);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return consumed;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_start_on_exit(void* context) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    submenu_reset(app->submenu);
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,29 @@
 | 
			
		||||
#include "../subghz_test_app_i.h"
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_static_callback(SubGhzTestStaticEvent event, void* context) {
 | 
			
		||||
    furi_assert(context);
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    view_dispatcher_send_custom_event(app->view_dispatcher, event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_static_on_enter(void* context) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    subghz_test_static_set_callback(
 | 
			
		||||
        app->subghz_test_static, subghz_test_scene_static_callback, app);
 | 
			
		||||
    view_dispatcher_switch_to_view(app->view_dispatcher, SubGhzTestViewStatic);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool subghz_test_scene_static_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SubGhzTestStaticEventOnlyRx) {
 | 
			
		||||
            scene_manager_next_scene(app->scene_manager, SubGhzTestSceneShowOnlyRx);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_test_scene_static_on_exit(void* context) {
 | 
			
		||||
    UNUSED(context);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								applications/debug/subghz_test/subghz_test_10px.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 181 B  | 
							
								
								
									
										139
									
								
								applications/debug/subghz_test/subghz_test_app.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,139 @@
 | 
			
		||||
#include "subghz_test_app_i.h"
 | 
			
		||||
 | 
			
		||||
#include <furi.h>
 | 
			
		||||
#include <furi_hal.h>
 | 
			
		||||
 | 
			
		||||
static bool subghz_test_app_custom_event_callback(void* context, uint32_t event) {
 | 
			
		||||
    furi_assert(context);
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    return scene_manager_handle_custom_event(app->scene_manager, event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool subghz_test_app_back_event_callback(void* context) {
 | 
			
		||||
    furi_assert(context);
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    return scene_manager_handle_back_event(app->scene_manager);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void subghz_test_app_tick_event_callback(void* context) {
 | 
			
		||||
    furi_assert(context);
 | 
			
		||||
    SubGhzTestApp* app = context;
 | 
			
		||||
    scene_manager_handle_tick_event(app->scene_manager);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SubGhzTestApp* subghz_test_app_alloc() {
 | 
			
		||||
    SubGhzTestApp* app = malloc(sizeof(SubGhzTestApp));
 | 
			
		||||
 | 
			
		||||
    // GUI
 | 
			
		||||
    app->gui = furi_record_open(RECORD_GUI);
 | 
			
		||||
 | 
			
		||||
    // View Dispatcher
 | 
			
		||||
    app->view_dispatcher = view_dispatcher_alloc();
 | 
			
		||||
    app->scene_manager = scene_manager_alloc(&subghz_test_scene_handlers, app);
 | 
			
		||||
    view_dispatcher_enable_queue(app->view_dispatcher);
 | 
			
		||||
 | 
			
		||||
    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
 | 
			
		||||
    view_dispatcher_set_custom_event_callback(
 | 
			
		||||
        app->view_dispatcher, subghz_test_app_custom_event_callback);
 | 
			
		||||
    view_dispatcher_set_navigation_event_callback(
 | 
			
		||||
        app->view_dispatcher, subghz_test_app_back_event_callback);
 | 
			
		||||
    view_dispatcher_set_tick_event_callback(
 | 
			
		||||
        app->view_dispatcher, subghz_test_app_tick_event_callback, 100);
 | 
			
		||||
 | 
			
		||||
    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
 | 
			
		||||
 | 
			
		||||
    // Open Notification record
 | 
			
		||||
    app->notifications = furi_record_open(RECORD_NOTIFICATION);
 | 
			
		||||
 | 
			
		||||
    // SubMenu
 | 
			
		||||
    app->submenu = submenu_alloc();
 | 
			
		||||
    view_dispatcher_add_view(
 | 
			
		||||
        app->view_dispatcher, SubGhzTestViewSubmenu, submenu_get_view(app->submenu));
 | 
			
		||||
 | 
			
		||||
    // Widget
 | 
			
		||||
    app->widget = widget_alloc();
 | 
			
		||||
    view_dispatcher_add_view(
 | 
			
		||||
        app->view_dispatcher, SubGhzTestViewWidget, widget_get_view(app->widget));
 | 
			
		||||
 | 
			
		||||
    // Popup
 | 
			
		||||
    app->popup = popup_alloc();
 | 
			
		||||
    view_dispatcher_add_view(
 | 
			
		||||
        app->view_dispatcher, SubGhzTestViewPopup, popup_get_view(app->popup));
 | 
			
		||||
 | 
			
		||||
    // Carrier Test Module
 | 
			
		||||
    app->subghz_test_carrier = subghz_test_carrier_alloc();
 | 
			
		||||
    view_dispatcher_add_view(
 | 
			
		||||
        app->view_dispatcher,
 | 
			
		||||
        SubGhzTestViewCarrier,
 | 
			
		||||
        subghz_test_carrier_get_view(app->subghz_test_carrier));
 | 
			
		||||
 | 
			
		||||
    // Packet Test
 | 
			
		||||
    app->subghz_test_packet = subghz_test_packet_alloc();
 | 
			
		||||
    view_dispatcher_add_view(
 | 
			
		||||
        app->view_dispatcher,
 | 
			
		||||
        SubGhzTestViewPacket,
 | 
			
		||||
        subghz_test_packet_get_view(app->subghz_test_packet));
 | 
			
		||||
 | 
			
		||||
    // Static send
 | 
			
		||||
    app->subghz_test_static = subghz_test_static_alloc();
 | 
			
		||||
    view_dispatcher_add_view(
 | 
			
		||||
        app->view_dispatcher,
 | 
			
		||||
        SubGhzTestViewStatic,
 | 
			
		||||
        subghz_test_static_get_view(app->subghz_test_static));
 | 
			
		||||
 | 
			
		||||
    scene_manager_next_scene(app->scene_manager, SubGhzTestSceneStart);
 | 
			
		||||
 | 
			
		||||
    return app;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_test_app_free(SubGhzTestApp* app) {
 | 
			
		||||
    furi_assert(app);
 | 
			
		||||
 | 
			
		||||
    // Submenu
 | 
			
		||||
    view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewSubmenu);
 | 
			
		||||
    submenu_free(app->submenu);
 | 
			
		||||
 | 
			
		||||
    //  Widget
 | 
			
		||||
    view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewWidget);
 | 
			
		||||
    widget_free(app->widget);
 | 
			
		||||
 | 
			
		||||
    // Popup
 | 
			
		||||
    view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewPopup);
 | 
			
		||||
    popup_free(app->popup);
 | 
			
		||||
 | 
			
		||||
    // Carrier Test
 | 
			
		||||
    view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewCarrier);
 | 
			
		||||
    subghz_test_carrier_free(app->subghz_test_carrier);
 | 
			
		||||
 | 
			
		||||
    // Packet Test
 | 
			
		||||
    view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewPacket);
 | 
			
		||||
    subghz_test_packet_free(app->subghz_test_packet);
 | 
			
		||||
 | 
			
		||||
    // Static
 | 
			
		||||
    view_dispatcher_remove_view(app->view_dispatcher, SubGhzTestViewStatic);
 | 
			
		||||
    subghz_test_static_free(app->subghz_test_static);
 | 
			
		||||
 | 
			
		||||
    // View dispatcher
 | 
			
		||||
    view_dispatcher_free(app->view_dispatcher);
 | 
			
		||||
    scene_manager_free(app->scene_manager);
 | 
			
		||||
 | 
			
		||||
    // Notifications
 | 
			
		||||
    furi_record_close(RECORD_NOTIFICATION);
 | 
			
		||||
    app->notifications = NULL;
 | 
			
		||||
 | 
			
		||||
    // Close records
 | 
			
		||||
    furi_record_close(RECORD_GUI);
 | 
			
		||||
 | 
			
		||||
    free(app);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int32_t subghz_test_app(void* p) {
 | 
			
		||||
    UNUSED(p);
 | 
			
		||||
    SubGhzTestApp* subghz_test_app = subghz_test_app_alloc();
 | 
			
		||||
 | 
			
		||||
    view_dispatcher_run(subghz_test_app->view_dispatcher);
 | 
			
		||||
 | 
			
		||||
    subghz_test_app_free(subghz_test_app);
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								applications/debug/subghz_test/subghz_test_app_i.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,5 @@
 | 
			
		||||
#include "subghz_test_app_i.h"
 | 
			
		||||
 | 
			
		||||
#include <furi.h>
 | 
			
		||||
 | 
			
		||||
#define TAG "SubGhzTest"
 | 
			
		||||
							
								
								
									
										32
									
								
								applications/debug/subghz_test/subghz_test_app_i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,32 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "helpers/subghz_test_types.h"
 | 
			
		||||
#include "helpers/subghz_test_event.h"
 | 
			
		||||
 | 
			
		||||
#include "scenes/subghz_test_scene.h"
 | 
			
		||||
#include <gui/gui.h>
 | 
			
		||||
#include <gui/view_dispatcher.h>
 | 
			
		||||
#include <gui/scene_manager.h>
 | 
			
		||||
#include <gui/modules/submenu.h>
 | 
			
		||||
#include <gui/modules/widget.h>
 | 
			
		||||
#include <gui/modules/popup.h>
 | 
			
		||||
#include <notification/notification_messages.h>
 | 
			
		||||
 | 
			
		||||
#include "views/subghz_test_static.h"
 | 
			
		||||
#include "views/subghz_test_carrier.h"
 | 
			
		||||
#include "views/subghz_test_packet.h"
 | 
			
		||||
 | 
			
		||||
typedef struct SubGhzTestApp SubGhzTestApp;
 | 
			
		||||
 | 
			
		||||
struct SubGhzTestApp {
 | 
			
		||||
    Gui* gui;
 | 
			
		||||
    ViewDispatcher* view_dispatcher;
 | 
			
		||||
    SceneManager* scene_manager;
 | 
			
		||||
    NotificationApp* notifications;
 | 
			
		||||
    Submenu* submenu;
 | 
			
		||||
    Widget* widget;
 | 
			
		||||
    Popup* popup;
 | 
			
		||||
    SubGhzTestStatic* subghz_test_static;
 | 
			
		||||
    SubGhzTestCarrier* subghz_test_carrier;
 | 
			
		||||
    SubGhzTestPacket* subghz_test_packet;
 | 
			
		||||
};
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
#include "subghz_test_carrier.h"
 | 
			
		||||
#include "../subghz_i.h"
 | 
			
		||||
#include "../helpers/subghz_testing.h"
 | 
			
		||||
#include "../subghz_test_app_i.h"
 | 
			
		||||
#include "../helpers/subghz_test_frequency.h"
 | 
			
		||||
#include <lib/subghz/devices/cc1101_configs.h>
 | 
			
		||||
 | 
			
		||||
#include <math.h>
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
#include "subghz_test_packet.h"
 | 
			
		||||
#include "../subghz_i.h"
 | 
			
		||||
#include "../helpers/subghz_testing.h"
 | 
			
		||||
#include "../subghz_test_app_i.h"
 | 
			
		||||
#include "../helpers/subghz_test_frequency.h"
 | 
			
		||||
#include <lib/subghz/devices/cc1101_configs.h>
 | 
			
		||||
 | 
			
		||||
#include <math.h>
 | 
			
		||||
@ -8,7 +8,7 @@
 | 
			
		||||
#include <furi_hal.h>
 | 
			
		||||
#include <input/input.h>
 | 
			
		||||
#include <toolbox/level_duration.h>
 | 
			
		||||
#include <lib/subghz/protocols/princeton_for_testing.h>
 | 
			
		||||
#include "../protocol/princeton_for_testing.h"
 | 
			
		||||
 | 
			
		||||
#define SUBGHZ_TEST_PACKET_COUNT 500
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
#include "subghz_test_static.h"
 | 
			
		||||
#include "../subghz_i.h"
 | 
			
		||||
#include "../helpers/subghz_testing.h"
 | 
			
		||||
#include "../subghz_test_app_i.h"
 | 
			
		||||
#include "../helpers/subghz_test_frequency.h"
 | 
			
		||||
#include <lib/subghz/devices/cc1101_configs.h>
 | 
			
		||||
 | 
			
		||||
#include <math.h>
 | 
			
		||||
@ -8,7 +8,7 @@
 | 
			
		||||
#include <furi_hal.h>
 | 
			
		||||
#include <input/input.h>
 | 
			
		||||
#include <notification/notification_messages.h>
 | 
			
		||||
#include <lib/subghz/protocols/princeton_for_testing.h>
 | 
			
		||||
#include "../protocol/princeton_for_testing.h"
 | 
			
		||||
 | 
			
		||||
#define TAG "SubGhzTestStatic"
 | 
			
		||||
 | 
			
		||||
@ -425,6 +425,7 @@ MU_TEST(infrared_test_decoder_mixed) {
 | 
			
		||||
    infrared_test_run_decoder(InfraredProtocolSamsung32, 1);
 | 
			
		||||
    infrared_test_run_decoder(InfraredProtocolSIRC, 3);
 | 
			
		||||
    infrared_test_run_decoder(InfraredProtocolKaseikyo, 1);
 | 
			
		||||
    infrared_test_run_decoder(InfraredProtocolRCA, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MU_TEST(infrared_test_decoder_nec) {
 | 
			
		||||
@ -499,6 +500,15 @@ MU_TEST(infrared_test_decoder_kaseikyo) {
 | 
			
		||||
    infrared_test_run_decoder(InfraredProtocolKaseikyo, 6);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MU_TEST(infrared_test_decoder_rca) {
 | 
			
		||||
    infrared_test_run_decoder(InfraredProtocolRCA, 1);
 | 
			
		||||
    infrared_test_run_decoder(InfraredProtocolRCA, 2);
 | 
			
		||||
    infrared_test_run_decoder(InfraredProtocolRCA, 3);
 | 
			
		||||
    infrared_test_run_decoder(InfraredProtocolRCA, 4);
 | 
			
		||||
    infrared_test_run_decoder(InfraredProtocolRCA, 5);
 | 
			
		||||
    infrared_test_run_decoder(InfraredProtocolRCA, 6);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MU_TEST(infrared_test_encoder_decoder_all) {
 | 
			
		||||
    infrared_test_run_encoder_decoder(InfraredProtocolNEC, 1);
 | 
			
		||||
    infrared_test_run_encoder_decoder(InfraredProtocolNECext, 1);
 | 
			
		||||
@ -509,6 +519,7 @@ MU_TEST(infrared_test_encoder_decoder_all) {
 | 
			
		||||
    infrared_test_run_encoder_decoder(InfraredProtocolRC5, 1);
 | 
			
		||||
    infrared_test_run_encoder_decoder(InfraredProtocolSIRC, 1);
 | 
			
		||||
    infrared_test_run_encoder_decoder(InfraredProtocolKaseikyo, 1);
 | 
			
		||||
    infrared_test_run_encoder_decoder(InfraredProtocolRCA, 1);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
MU_TEST_SUITE(infrared_test) {
 | 
			
		||||
@ -527,6 +538,7 @@ MU_TEST_SUITE(infrared_test) {
 | 
			
		||||
    MU_RUN_TEST(infrared_test_decoder_samsung32);
 | 
			
		||||
    MU_RUN_TEST(infrared_test_decoder_necext1);
 | 
			
		||||
    MU_RUN_TEST(infrared_test_decoder_kaseikyo);
 | 
			
		||||
    MU_RUN_TEST(infrared_test_decoder_rca);
 | 
			
		||||
    MU_RUN_TEST(infrared_test_decoder_mixed);
 | 
			
		||||
    MU_RUN_TEST(infrared_test_encoder_decoder_all);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -4,5 +4,6 @@ App(
 | 
			
		||||
    targets=["f7"],
 | 
			
		||||
    entry_point="subghz_device_cc1101_ext_ep",
 | 
			
		||||
    requires=["subghz"],
 | 
			
		||||
    sdk_headers=["cc1101_ext/cc1101_ext_interconnect.h"],
 | 
			
		||||
    fap_libs=["hwdrivers"],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -18,7 +18,7 @@ Before launching the application, connect the sensor to Flipper's external GPIO
 | 
			
		||||
In order to launch this demo, follow the steps below:
 | 
			
		||||
1. Make sure your Flipper has an SD card installed.
 | 
			
		||||
2. Connect your Flipper to the computer via a USB cable.
 | 
			
		||||
3. Run `./fbt launch_app APPSRC=example_thermo` in your terminal emulator of choice.
 | 
			
		||||
3. Run `./fbt launch APPSRC=example_thermo` in your terminal emulator of choice.
 | 
			
		||||
 | 
			
		||||
## Changing the data pin
 | 
			
		||||
It is possible to use other GPIO pin as a 1-Wire data pin. In order to change it, set the `THERMO_GPIO_PIN` macro to any of the options listed below:
 | 
			
		||||
 | 
			
		||||
@ -116,9 +116,11 @@ static ManchesterEvent level_and_duration_to_event(bool level, uint32_t duration
 | 
			
		||||
static uint8_t oregon3_sensor_id_var_bits(uint16_t sensor_id) {
 | 
			
		||||
    switch(sensor_id) {
 | 
			
		||||
    case ID_THGR221:
 | 
			
		||||
    default:
 | 
			
		||||
        // nibbles: temp + hum + '0'
 | 
			
		||||
        return (4 + 2 + 1) * 4;
 | 
			
		||||
    default:
 | 
			
		||||
        FURI_LOG_D(TAG, "Unsupported sensor id 0x%x", sensor_id);
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -198,10 +200,8 @@ void ws_protocol_decoder_oregon3_feed(void* context, bool level, uint32_t durati
 | 
			
		||||
                oregon3_sensor_id_var_bits(OREGON3_SENSOR_ID(instance->generic.data));
 | 
			
		||||
 | 
			
		||||
            if(!instance->var_bits) {
 | 
			
		||||
                // sensor is not supported, stop decoding, but showing the decoded fixed part
 | 
			
		||||
                // sensor is not supported, stop decoding
 | 
			
		||||
                instance->decoder.parser_step = Oregon3DecoderStepReset;
 | 
			
		||||
                if(instance->base.callback)
 | 
			
		||||
                    instance->base.callback(&instance->base, instance->base.context);
 | 
			
		||||
            } else {
 | 
			
		||||
                instance->decoder.parser_step = Oregon3DecoderStepVarData;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,6 @@ App(
 | 
			
		||||
    apptype=FlipperAppType.METAPACKAGE,
 | 
			
		||||
    provides=[
 | 
			
		||||
        "gpio",
 | 
			
		||||
        "onewire",
 | 
			
		||||
        "ibutton",
 | 
			
		||||
        "infrared",
 | 
			
		||||
        "lfrfid",
 | 
			
		||||
@ -13,5 +12,20 @@ App(
 | 
			
		||||
        "bad_usb",
 | 
			
		||||
        "u2f",
 | 
			
		||||
        "archive",
 | 
			
		||||
        "main_apps_on_start",
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
App(
 | 
			
		||||
    appid="main_apps_on_start",
 | 
			
		||||
    name="On start hooks",
 | 
			
		||||
    apptype=FlipperAppType.METAPACKAGE,
 | 
			
		||||
    provides=[
 | 
			
		||||
        "ibutton_start",
 | 
			
		||||
        "onewire_start",
 | 
			
		||||
        "subghz_start",
 | 
			
		||||
        "infrared_start",
 | 
			
		||||
        "lfrfid_start",
 | 
			
		||||
        "nfc_start",
 | 
			
		||||
    ],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -64,8 +64,20 @@ static void
 | 
			
		||||
    if(!is_last) {
 | 
			
		||||
        archive_add_file_item(browser, is_folder, furi_string_get_cstr(item_path));
 | 
			
		||||
    } else {
 | 
			
		||||
        bool load_again = false;
 | 
			
		||||
        with_view_model(
 | 
			
		||||
            browser->view, ArchiveBrowserViewModel * model, { model->list_loading = false; }, true);
 | 
			
		||||
            browser->view,
 | 
			
		||||
            ArchiveBrowserViewModel * model,
 | 
			
		||||
            {
 | 
			
		||||
                model->list_loading = false;
 | 
			
		||||
                if(archive_is_file_list_load_required(model)) {
 | 
			
		||||
                    load_again = true;
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            true);
 | 
			
		||||
        if(load_again) {
 | 
			
		||||
            archive_file_array_load(browser, 0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -111,6 +123,26 @@ bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx) {
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model) {
 | 
			
		||||
    size_t array_size = files_array_size(model->files);
 | 
			
		||||
 | 
			
		||||
    if((model->list_loading) || (array_size >= model->item_cnt)) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if((model->array_offset > 0) &&
 | 
			
		||||
       (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(((model->array_offset + array_size) < model->item_cnt) &&
 | 
			
		||||
       (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void archive_update_offset(ArchiveBrowserView* browser) {
 | 
			
		||||
    furi_assert(browser);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -64,6 +64,7 @@ inline bool archive_is_known_app(ArchiveFileTypeEnum type) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool archive_is_item_in_array(ArchiveBrowserViewModel* model, uint32_t idx);
 | 
			
		||||
bool archive_is_file_list_load_required(ArchiveBrowserViewModel* model);
 | 
			
		||||
void archive_update_offset(ArchiveBrowserView* browser);
 | 
			
		||||
void archive_update_focus(ArchiveBrowserView* browser, const char* target);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -5,13 +5,14 @@
 | 
			
		||||
#include "../helpers/archive_browser.h"
 | 
			
		||||
#include "../views/archive_browser_view.h"
 | 
			
		||||
#include "archive/scenes/archive_scene.h"
 | 
			
		||||
#include <applications.h>
 | 
			
		||||
 | 
			
		||||
#define TAG "ArchiveSceneBrowser"
 | 
			
		||||
 | 
			
		||||
#define SCENE_STATE_DEFAULT (0)
 | 
			
		||||
#define SCENE_STATE_NEED_REFRESH (1)
 | 
			
		||||
 | 
			
		||||
const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) {
 | 
			
		||||
static const char* archive_get_flipper_app_name(ArchiveFileTypeEnum file_type) {
 | 
			
		||||
    switch(file_type) {
 | 
			
		||||
    case ArchiveFileTypeIButton:
 | 
			
		||||
        return "iButton";
 | 
			
		||||
 | 
			
		||||
@ -248,24 +248,10 @@ View* archive_browser_get_view(ArchiveBrowserView* browser) {
 | 
			
		||||
    return browser->view;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool is_file_list_load_required(ArchiveBrowserViewModel* model) {
 | 
			
		||||
    size_t array_size = files_array_size(model->files);
 | 
			
		||||
 | 
			
		||||
    if((model->list_loading) || (array_size >= model->item_cnt)) {
 | 
			
		||||
        return false;
 | 
			
		||||
static void file_list_rollover(ArchiveBrowserViewModel* model) {
 | 
			
		||||
    if(!model->list_loading && files_array_size(model->files) < model->item_cnt) {
 | 
			
		||||
        files_array_reset(model->files);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if((model->array_offset > 0) &&
 | 
			
		||||
       (model->item_idx < (model->array_offset + FILE_LIST_BUF_LEN / 4))) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(((model->array_offset + array_size) < model->item_cnt) &&
 | 
			
		||||
       (model->item_idx > (int32_t)(model->array_offset + array_size - FILE_LIST_BUF_LEN / 4))) {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool archive_view_input(InputEvent* event, void* context) {
 | 
			
		||||
@ -347,12 +333,13 @@ static bool archive_view_input(InputEvent* event, void* context) {
 | 
			
		||||
                        if(model->item_idx < scroll_speed) {
 | 
			
		||||
                            model->button_held_for_ticks = 0;
 | 
			
		||||
                            model->item_idx = model->item_cnt - 1;
 | 
			
		||||
                            file_list_rollover(model);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            model->item_idx =
 | 
			
		||||
                                ((model->item_idx - scroll_speed) + model->item_cnt) %
 | 
			
		||||
                                model->item_cnt;
 | 
			
		||||
                        }
 | 
			
		||||
                        if(is_file_list_load_required(model)) {
 | 
			
		||||
                        if(archive_is_file_list_load_required(model)) {
 | 
			
		||||
                            model->list_loading = true;
 | 
			
		||||
                            browser->callback(ArchiveBrowserEventLoadPrevItems, browser->context);
 | 
			
		||||
                        }
 | 
			
		||||
@ -366,10 +353,11 @@ static bool archive_view_input(InputEvent* event, void* context) {
 | 
			
		||||
                        if(model->item_idx + scroll_speed >= count) {
 | 
			
		||||
                            model->button_held_for_ticks = 0;
 | 
			
		||||
                            model->item_idx = 0;
 | 
			
		||||
                            file_list_rollover(model);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt;
 | 
			
		||||
                        }
 | 
			
		||||
                        if(is_file_list_load_required(model)) {
 | 
			
		||||
                        if(archive_is_file_list_load_required(model)) {
 | 
			
		||||
                            model->list_loading = true;
 | 
			
		||||
                            browser->callback(ArchiveBrowserEventLoadNextItems, browser->context);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
@ -1,15 +1,12 @@
 | 
			
		||||
App(
 | 
			
		||||
    appid="bad_usb",
 | 
			
		||||
    name="Bad USB",
 | 
			
		||||
    apptype=FlipperAppType.APP,
 | 
			
		||||
    apptype=FlipperAppType.MENUEXTERNAL,
 | 
			
		||||
    entry_point="bad_usb_app",
 | 
			
		||||
    cdefines=["APP_BAD_USB"],
 | 
			
		||||
    requires=[
 | 
			
		||||
        "gui",
 | 
			
		||||
        "dialogs",
 | 
			
		||||
    ],
 | 
			
		||||
    stack_size=2 * 1024,
 | 
			
		||||
    icon="A_BadUsb_14",
 | 
			
		||||
    order=70,
 | 
			
		||||
    fap_libs=["assets"],
 | 
			
		||||
    fap_icon="icon.png",
 | 
			
		||||
    fap_category="USB",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								applications/main/bad_usb/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 576 B  | 
@ -1,12 +1,12 @@
 | 
			
		||||
App(
 | 
			
		||||
    appid="gpio",
 | 
			
		||||
    name="GPIO",
 | 
			
		||||
    apptype=FlipperAppType.APP,
 | 
			
		||||
    apptype=FlipperAppType.MENUEXTERNAL,
 | 
			
		||||
    entry_point="gpio_app",
 | 
			
		||||
    cdefines=["APP_GPIO"],
 | 
			
		||||
    requires=["gui"],
 | 
			
		||||
    stack_size=1 * 1024,
 | 
			
		||||
    icon="A_GPIO_14",
 | 
			
		||||
    order=50,
 | 
			
		||||
    fap_libs=["assets"],
 | 
			
		||||
    fap_icon="icon.png",
 | 
			
		||||
    fap_category="GPIO",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								applications/main/gpio/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.7 KiB  | 
@ -1,25 +1,21 @@
 | 
			
		||||
App(
 | 
			
		||||
    appid="ibutton",
 | 
			
		||||
    name="iButton",
 | 
			
		||||
    apptype=FlipperAppType.APP,
 | 
			
		||||
    apptype=FlipperAppType.MENUEXTERNAL,
 | 
			
		||||
    targets=["f7"],
 | 
			
		||||
    entry_point="ibutton_app",
 | 
			
		||||
    cdefines=["APP_IBUTTON"],
 | 
			
		||||
    requires=[
 | 
			
		||||
        "gui",
 | 
			
		||||
        "dialogs",
 | 
			
		||||
    ],
 | 
			
		||||
    provides=["ibutton_start"],
 | 
			
		||||
    icon="A_iButton_14",
 | 
			
		||||
    stack_size=2 * 1024,
 | 
			
		||||
    order=60,
 | 
			
		||||
    fap_libs=["assets"],
 | 
			
		||||
    fap_icon="icon.png",
 | 
			
		||||
    fap_category="iButton",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
App(
 | 
			
		||||
    appid="ibutton_start",
 | 
			
		||||
    apptype=FlipperAppType.STARTUP,
 | 
			
		||||
    targets=["f7"],
 | 
			
		||||
    entry_point="ibutton_on_system_start",
 | 
			
		||||
    requires=["ibutton"],
 | 
			
		||||
    order=60,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								applications/main/ibutton/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 304 B  | 
@ -12,15 +12,15 @@ void ibutton_scene_delete_confirm_on_enter(void* context) {
 | 
			
		||||
    widget_add_button_element(
 | 
			
		||||
        widget, GuiButtonTypeRight, "Delete", ibutton_widget_callback, context);
 | 
			
		||||
 | 
			
		||||
    furi_string_printf(tmp, "Delete %s?", ibutton->key_name);
 | 
			
		||||
    widget_add_string_element(
 | 
			
		||||
        widget, 128 / 2, 0, AlignCenter, AlignTop, FontPrimary, furi_string_get_cstr(tmp));
 | 
			
		||||
    furi_string_printf(tmp, "\e#Delete %s?\e#", ibutton->key_name);
 | 
			
		||||
    widget_add_text_box_element(
 | 
			
		||||
        widget, 0, 0, 128, 23, AlignCenter, AlignCenter, furi_string_get_cstr(tmp), false);
 | 
			
		||||
 | 
			
		||||
    furi_string_reset(tmp);
 | 
			
		||||
    ibutton_protocols_render_brief_data(ibutton->protocols, key, tmp);
 | 
			
		||||
 | 
			
		||||
    widget_add_string_multiline_element(
 | 
			
		||||
        widget, 128 / 2, 16, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
 | 
			
		||||
        widget, 128 / 2, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(tmp));
 | 
			
		||||
 | 
			
		||||
    view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget);
 | 
			
		||||
    furi_string_free(tmp);
 | 
			
		||||
 | 
			
		||||
@ -1,25 +1,21 @@
 | 
			
		||||
App(
 | 
			
		||||
    appid="infrared",
 | 
			
		||||
    name="Infrared",
 | 
			
		||||
    apptype=FlipperAppType.APP,
 | 
			
		||||
    apptype=FlipperAppType.MENUEXTERNAL,
 | 
			
		||||
    entry_point="infrared_app",
 | 
			
		||||
    targets=["f7"],
 | 
			
		||||
    cdefines=["APP_INFRARED"],
 | 
			
		||||
    requires=[
 | 
			
		||||
        "gui",
 | 
			
		||||
        "dialogs",
 | 
			
		||||
    ],
 | 
			
		||||
    provides=["infrared_start"],
 | 
			
		||||
    icon="A_Infrared_14",
 | 
			
		||||
    stack_size=3 * 1024,
 | 
			
		||||
    order=40,
 | 
			
		||||
    fap_libs=["assets"],
 | 
			
		||||
    fap_icon="icon.png",
 | 
			
		||||
    fap_category="Infrared",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
App(
 | 
			
		||||
    appid="infrared_start",
 | 
			
		||||
    apptype=FlipperAppType.STARTUP,
 | 
			
		||||
    targets=["f7"],
 | 
			
		||||
    entry_point="infrared_on_system_start",
 | 
			
		||||
    requires=["infrared"],
 | 
			
		||||
    order=20,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								applications/main/infrared/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 305 B  | 
@ -1,27 +1,21 @@
 | 
			
		||||
App(
 | 
			
		||||
    appid="lfrfid",
 | 
			
		||||
    name="125 kHz RFID",
 | 
			
		||||
    apptype=FlipperAppType.APP,
 | 
			
		||||
    apptype=FlipperAppType.MENUEXTERNAL,
 | 
			
		||||
    targets=["f7"],
 | 
			
		||||
    entry_point="lfrfid_app",
 | 
			
		||||
    cdefines=["APP_LF_RFID"],
 | 
			
		||||
    requires=[
 | 
			
		||||
        "gui",
 | 
			
		||||
        "dialogs",
 | 
			
		||||
    ],
 | 
			
		||||
    provides=[
 | 
			
		||||
        "lfrfid_start",
 | 
			
		||||
    ],
 | 
			
		||||
    icon="A_125khz_14",
 | 
			
		||||
    stack_size=2 * 1024,
 | 
			
		||||
    order=20,
 | 
			
		||||
    fap_libs=["assets"],
 | 
			
		||||
    fap_icon="icon.png",
 | 
			
		||||
    fap_category="RFID",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
App(
 | 
			
		||||
    appid="lfrfid_start",
 | 
			
		||||
    targets=["f7"],
 | 
			
		||||
    apptype=FlipperAppType.STARTUP,
 | 
			
		||||
    entry_point="lfrfid_on_system_start",
 | 
			
		||||
    requires=["lfrfid"],
 | 
			
		||||
    order=50,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								applications/main/lfrfid/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 308 B  | 
@ -1,24 +1,21 @@
 | 
			
		||||
App(
 | 
			
		||||
    appid="nfc",
 | 
			
		||||
    name="NFC",
 | 
			
		||||
    apptype=FlipperAppType.APP,
 | 
			
		||||
    apptype=FlipperAppType.MENUEXTERNAL,
 | 
			
		||||
    targets=["f7"],
 | 
			
		||||
    entry_point="nfc_app",
 | 
			
		||||
    cdefines=["APP_NFC"],
 | 
			
		||||
    requires=[
 | 
			
		||||
        "gui",
 | 
			
		||||
        "dialogs",
 | 
			
		||||
    ],
 | 
			
		||||
    provides=["nfc_start"],
 | 
			
		||||
    icon="A_NFC_14",
 | 
			
		||||
    stack_size=5 * 1024,
 | 
			
		||||
    order=30,
 | 
			
		||||
    fap_libs=["assets"],
 | 
			
		||||
    fap_icon="icon.png",
 | 
			
		||||
    fap_category="NFC",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
App(
 | 
			
		||||
    appid="nfc_start",
 | 
			
		||||
    targets=["f7"],
 | 
			
		||||
    apptype=FlipperAppType.STARTUP,
 | 
			
		||||
    entry_point="nfc_on_system_start",
 | 
			
		||||
    requires=["nfc"],
 | 
			
		||||
    order=30,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								applications/main/nfc/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 304 B  | 
@ -1,14 +1,6 @@
 | 
			
		||||
App(
 | 
			
		||||
    appid="onewire",
 | 
			
		||||
    name="1-Wire",
 | 
			
		||||
    apptype=FlipperAppType.METAPACKAGE,
 | 
			
		||||
    provides=["onewire_start"],
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
App(
 | 
			
		||||
    appid="onewire_start",
 | 
			
		||||
    apptype=FlipperAppType.STARTUP,
 | 
			
		||||
    entry_point="onewire_on_system_start",
 | 
			
		||||
    requires=["onewire"],
 | 
			
		||||
    order=60,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -1,25 +1,21 @@
 | 
			
		||||
App(
 | 
			
		||||
    appid="subghz",
 | 
			
		||||
    name="Sub-GHz",
 | 
			
		||||
    apptype=FlipperAppType.APP,
 | 
			
		||||
    apptype=FlipperAppType.MENUEXTERNAL,
 | 
			
		||||
    targets=["f7"],
 | 
			
		||||
    entry_point="subghz_app",
 | 
			
		||||
    cdefines=["APP_SUBGHZ"],
 | 
			
		||||
    requires=[
 | 
			
		||||
        "gui",
 | 
			
		||||
        "cli",
 | 
			
		||||
        "dialogs",
 | 
			
		||||
    ],
 | 
			
		||||
    provides=["subghz_start"],
 | 
			
		||||
    icon="A_Sub1ghz_14",
 | 
			
		||||
    stack_size=3 * 1024,
 | 
			
		||||
    order=10,
 | 
			
		||||
    fap_libs=["assets", "hwdrivers"],
 | 
			
		||||
    fap_icon="icon.png",
 | 
			
		||||
    fap_category="Sub-GHz",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
App(
 | 
			
		||||
    appid="subghz_start",
 | 
			
		||||
    targets=["f7"],
 | 
			
		||||
    apptype=FlipperAppType.STARTUP,
 | 
			
		||||
    entry_point="subghz_on_system_start",
 | 
			
		||||
    requires=["subghz"],
 | 
			
		||||
    order=40,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -80,9 +80,6 @@ typedef enum {
 | 
			
		||||
    SubGhzViewIdFrequencyAnalyzer,
 | 
			
		||||
    SubGhzViewIdReadRAW,
 | 
			
		||||
 | 
			
		||||
    SubGhzViewIdStatic,
 | 
			
		||||
    SubGhzViewIdTestCarrier,
 | 
			
		||||
    SubGhzViewIdTestPacket,
 | 
			
		||||
} SubGhzViewId;
 | 
			
		||||
 | 
			
		||||
/** SubGhz load type file */
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								applications/main/subghz/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 299 B  | 
@ -12,10 +12,6 @@ ADD_SCENE(subghz, show_only_rx, ShowOnlyRx)
 | 
			
		||||
ADD_SCENE(subghz, saved_menu, SavedMenu)
 | 
			
		||||
ADD_SCENE(subghz, delete, Delete)
 | 
			
		||||
ADD_SCENE(subghz, delete_success, DeleteSuccess)
 | 
			
		||||
ADD_SCENE(subghz, test, Test)
 | 
			
		||||
ADD_SCENE(subghz, test_static, TestStatic)
 | 
			
		||||
ADD_SCENE(subghz, test_carrier, TestCarrier)
 | 
			
		||||
ADD_SCENE(subghz, test_packet, TestPacket)
 | 
			
		||||
ADD_SCENE(subghz, set_type, SetType)
 | 
			
		||||
ADD_SCENE(subghz, frequency_analyzer, FrequencyAnalyzer)
 | 
			
		||||
ADD_SCENE(subghz, read_raw, ReadRAW)
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,6 @@
 | 
			
		||||
enum SubmenuIndex {
 | 
			
		||||
    SubmenuIndexRead = 10,
 | 
			
		||||
    SubmenuIndexSaved,
 | 
			
		||||
    SubmenuIndexTest,
 | 
			
		||||
    SubmenuIndexAddManually,
 | 
			
		||||
    SubmenuIndexFrequencyAnalyzer,
 | 
			
		||||
    SubmenuIndexReadRAW,
 | 
			
		||||
@ -56,10 +55,6 @@ void subghz_scene_start_on_enter(void* context) {
 | 
			
		||||
        SubmenuIndexRadioSetting,
 | 
			
		||||
        subghz_scene_start_submenu_callback,
 | 
			
		||||
        subghz);
 | 
			
		||||
    if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
 | 
			
		||||
        submenu_add_item(
 | 
			
		||||
            subghz->submenu, "Test", SubmenuIndexTest, subghz_scene_start_submenu_callback, subghz);
 | 
			
		||||
    }
 | 
			
		||||
    submenu_set_selected_item(
 | 
			
		||||
        subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneStart));
 | 
			
		||||
 | 
			
		||||
@ -101,11 +96,6 @@ bool subghz_scene_start_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneFrequencyAnalyzer);
 | 
			
		||||
            dolphin_deed(DolphinDeedSubGhzFrequencyAnalyzer);
 | 
			
		||||
            return true;
 | 
			
		||||
        } else if(event.event == SubmenuIndexTest) {
 | 
			
		||||
            scene_manager_set_scene_state(
 | 
			
		||||
                subghz->scene_manager, SubGhzSceneStart, SubmenuIndexTest);
 | 
			
		||||
            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTest);
 | 
			
		||||
            return true;
 | 
			
		||||
        } else if(event.event == SubmenuIndexShowRegionInfo) {
 | 
			
		||||
            scene_manager_set_scene_state(
 | 
			
		||||
                subghz->scene_manager, SubGhzSceneStart, SubmenuIndexShowRegionInfo);
 | 
			
		||||
 | 
			
		||||
@ -1,61 +0,0 @@
 | 
			
		||||
#include "../subghz_i.h"
 | 
			
		||||
 | 
			
		||||
enum SubmenuIndex {
 | 
			
		||||
    SubmenuIndexCarrier,
 | 
			
		||||
    SubmenuIndexPacket,
 | 
			
		||||
    SubmenuIndexStatic,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
void subghz_scene_test_submenu_callback(void* context, uint32_t index) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    view_dispatcher_send_custom_event(subghz->view_dispatcher, index);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_scene_test_on_enter(void* context) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
 | 
			
		||||
    submenu_add_item(
 | 
			
		||||
        subghz->submenu,
 | 
			
		||||
        "Carrier",
 | 
			
		||||
        SubmenuIndexCarrier,
 | 
			
		||||
        subghz_scene_test_submenu_callback,
 | 
			
		||||
        subghz);
 | 
			
		||||
    submenu_add_item(
 | 
			
		||||
        subghz->submenu, "Packet", SubmenuIndexPacket, subghz_scene_test_submenu_callback, subghz);
 | 
			
		||||
    submenu_add_item(
 | 
			
		||||
        subghz->submenu, "Static", SubmenuIndexStatic, subghz_scene_test_submenu_callback, subghz);
 | 
			
		||||
 | 
			
		||||
    submenu_set_selected_item(
 | 
			
		||||
        subghz->submenu, scene_manager_get_scene_state(subghz->scene_manager, SubGhzSceneTest));
 | 
			
		||||
 | 
			
		||||
    view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdMenu);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool subghz_scene_test_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SubmenuIndexCarrier) {
 | 
			
		||||
            scene_manager_set_scene_state(
 | 
			
		||||
                subghz->scene_manager, SubGhzSceneTest, SubmenuIndexCarrier);
 | 
			
		||||
            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTestCarrier);
 | 
			
		||||
            return true;
 | 
			
		||||
        } else if(event.event == SubmenuIndexPacket) {
 | 
			
		||||
            scene_manager_set_scene_state(
 | 
			
		||||
                subghz->scene_manager, SubGhzSceneTest, SubmenuIndexPacket);
 | 
			
		||||
            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTestPacket);
 | 
			
		||||
            return true;
 | 
			
		||||
        } else if(event.event == SubmenuIndexStatic) {
 | 
			
		||||
            scene_manager_set_scene_state(
 | 
			
		||||
                subghz->scene_manager, SubGhzSceneTest, SubmenuIndexStatic);
 | 
			
		||||
            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneTestStatic);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_scene_test_on_exit(void* context) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    submenu_reset(subghz->submenu);
 | 
			
		||||
}
 | 
			
		||||
@ -1,30 +0,0 @@
 | 
			
		||||
#include "../subghz_i.h"
 | 
			
		||||
#include "../views/subghz_test_carrier.h"
 | 
			
		||||
 | 
			
		||||
void subghz_scene_test_carrier_callback(SubGhzTestCarrierEvent event, void* context) {
 | 
			
		||||
    furi_assert(context);
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_scene_test_carrier_on_enter(void* context) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    subghz_test_carrier_set_callback(
 | 
			
		||||
        subghz->subghz_test_carrier, subghz_scene_test_carrier_callback, subghz);
 | 
			
		||||
    view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTestCarrier);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool subghz_scene_test_carrier_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SubGhzTestCarrierEventOnlyRx) {
 | 
			
		||||
            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_scene_test_carrier_on_exit(void* context) {
 | 
			
		||||
    UNUSED(context);
 | 
			
		||||
}
 | 
			
		||||
@ -1,30 +0,0 @@
 | 
			
		||||
#include "../subghz_i.h"
 | 
			
		||||
#include "../views/subghz_test_packet.h"
 | 
			
		||||
 | 
			
		||||
void subghz_scene_test_packet_callback(SubGhzTestPacketEvent event, void* context) {
 | 
			
		||||
    furi_assert(context);
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_scene_test_packet_on_enter(void* context) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    subghz_test_packet_set_callback(
 | 
			
		||||
        subghz->subghz_test_packet, subghz_scene_test_packet_callback, subghz);
 | 
			
		||||
    view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdTestPacket);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool subghz_scene_test_packet_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SubGhzTestPacketEventOnlyRx) {
 | 
			
		||||
            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_scene_test_packet_on_exit(void* context) {
 | 
			
		||||
    UNUSED(context);
 | 
			
		||||
}
 | 
			
		||||
@ -1,30 +0,0 @@
 | 
			
		||||
#include "../subghz_i.h"
 | 
			
		||||
#include "../views/subghz_test_static.h"
 | 
			
		||||
 | 
			
		||||
void subghz_scene_test_static_callback(SubGhzTestStaticEvent event, void* context) {
 | 
			
		||||
    furi_assert(context);
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    view_dispatcher_send_custom_event(subghz->view_dispatcher, event);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_scene_test_static_on_enter(void* context) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    subghz_test_static_set_callback(
 | 
			
		||||
        subghz->subghz_test_static, subghz_scene_test_static_callback, subghz);
 | 
			
		||||
    view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdStatic);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool subghz_scene_test_static_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
    SubGhz* subghz = context;
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == SubGhzTestStaticEventOnlyRx) {
 | 
			
		||||
            scene_manager_next_scene(subghz->scene_manager, SubGhzSceneShowOnlyRx);
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void subghz_scene_test_static_on_exit(void* context) {
 | 
			
		||||
    UNUSED(context);
 | 
			
		||||
}
 | 
			
		||||
@ -129,27 +129,6 @@ SubGhz* subghz_alloc() {
 | 
			
		||||
        SubGhzViewIdReadRAW,
 | 
			
		||||
        subghz_read_raw_get_view(subghz->subghz_read_raw));
 | 
			
		||||
 | 
			
		||||
    // Carrier Test Module
 | 
			
		||||
    subghz->subghz_test_carrier = subghz_test_carrier_alloc();
 | 
			
		||||
    view_dispatcher_add_view(
 | 
			
		||||
        subghz->view_dispatcher,
 | 
			
		||||
        SubGhzViewIdTestCarrier,
 | 
			
		||||
        subghz_test_carrier_get_view(subghz->subghz_test_carrier));
 | 
			
		||||
 | 
			
		||||
    // Packet Test
 | 
			
		||||
    subghz->subghz_test_packet = subghz_test_packet_alloc();
 | 
			
		||||
    view_dispatcher_add_view(
 | 
			
		||||
        subghz->view_dispatcher,
 | 
			
		||||
        SubGhzViewIdTestPacket,
 | 
			
		||||
        subghz_test_packet_get_view(subghz->subghz_test_packet));
 | 
			
		||||
 | 
			
		||||
    // Static send
 | 
			
		||||
    subghz->subghz_test_static = subghz_test_static_alloc();
 | 
			
		||||
    view_dispatcher_add_view(
 | 
			
		||||
        subghz->view_dispatcher,
 | 
			
		||||
        SubGhzViewIdStatic,
 | 
			
		||||
        subghz_test_static_get_view(subghz->subghz_test_static));
 | 
			
		||||
 | 
			
		||||
    //init threshold rssi
 | 
			
		||||
    subghz->threshold_rssi = subghz_threshold_rssi_alloc();
 | 
			
		||||
 | 
			
		||||
@ -183,18 +162,6 @@ void subghz_free(SubGhz* subghz) {
 | 
			
		||||
    subghz_txrx_stop(subghz->txrx);
 | 
			
		||||
    subghz_txrx_sleep(subghz->txrx);
 | 
			
		||||
 | 
			
		||||
    // Packet Test
 | 
			
		||||
    view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestPacket);
 | 
			
		||||
    subghz_test_packet_free(subghz->subghz_test_packet);
 | 
			
		||||
 | 
			
		||||
    // Carrier Test
 | 
			
		||||
    view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdTestCarrier);
 | 
			
		||||
    subghz_test_carrier_free(subghz->subghz_test_carrier);
 | 
			
		||||
 | 
			
		||||
    // Static
 | 
			
		||||
    view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdStatic);
 | 
			
		||||
    subghz_test_static_free(subghz->subghz_test_static);
 | 
			
		||||
 | 
			
		||||
    // Receiver
 | 
			
		||||
    view_dispatcher_remove_view(subghz->view_dispatcher, SubGhzViewIdReceiver);
 | 
			
		||||
    subghz_view_receiver_free(subghz->subghz_receiver);
 | 
			
		||||
 | 
			
		||||
@ -9,10 +9,6 @@
 | 
			
		||||
#include "views/subghz_frequency_analyzer.h"
 | 
			
		||||
#include "views/subghz_read_raw.h"
 | 
			
		||||
 | 
			
		||||
#include "views/subghz_test_static.h"
 | 
			
		||||
#include "views/subghz_test_carrier.h"
 | 
			
		||||
#include "views/subghz_test_packet.h"
 | 
			
		||||
 | 
			
		||||
#include <gui/gui.h>
 | 
			
		||||
#include <assets_icons.h>
 | 
			
		||||
#include <dialogs/dialogs.h>
 | 
			
		||||
@ -64,9 +60,6 @@ struct SubGhz {
 | 
			
		||||
 | 
			
		||||
    SubGhzFrequencyAnalyzer* subghz_frequency_analyzer;
 | 
			
		||||
    SubGhzReadRAW* subghz_read_raw;
 | 
			
		||||
    SubGhzTestStatic* subghz_test_static;
 | 
			
		||||
    SubGhzTestCarrier* subghz_test_carrier;
 | 
			
		||||
    SubGhzTestPacket* subghz_test_packet;
 | 
			
		||||
 | 
			
		||||
    SubGhzProtocolFlag filter;
 | 
			
		||||
    FuriString* error_str;
 | 
			
		||||
 | 
			
		||||
@ -1,15 +1,12 @@
 | 
			
		||||
App(
 | 
			
		||||
    appid="u2f",
 | 
			
		||||
    name="U2F",
 | 
			
		||||
    apptype=FlipperAppType.APP,
 | 
			
		||||
    apptype=FlipperAppType.MENUEXTERNAL,
 | 
			
		||||
    entry_point="u2f_app",
 | 
			
		||||
    cdefines=["APP_U2F"],
 | 
			
		||||
    requires=[
 | 
			
		||||
        "gui",
 | 
			
		||||
        "dialogs",
 | 
			
		||||
    ],
 | 
			
		||||
    stack_size=2 * 1024,
 | 
			
		||||
    icon="A_U2F_14",
 | 
			
		||||
    order=80,
 | 
			
		||||
    fap_libs=["assets"],
 | 
			
		||||
    fap_category="USB",
 | 
			
		||||
    fap_icon="icon.png",
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								applications/main/u2f/icon.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 583 B  | 
@ -17,6 +17,12 @@ typedef struct {
 | 
			
		||||
    const FlipperInternalApplicationFlag flags;
 | 
			
		||||
} FlipperInternalApplication;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    const char* name;
 | 
			
		||||
    const Icon* icon;
 | 
			
		||||
    const char* path;
 | 
			
		||||
} FlipperExternalApplication;
 | 
			
		||||
 | 
			
		||||
typedef void (*FlipperInternalOnStartHook)(void);
 | 
			
		||||
 | 
			
		||||
extern const char* FLIPPER_AUTORUN_APP_NAME;
 | 
			
		||||
@ -52,3 +58,9 @@ extern const FlipperInternalApplication FLIPPER_ARCHIVE;
 | 
			
		||||
 */
 | 
			
		||||
extern const FlipperInternalApplication FLIPPER_SETTINGS_APPS[];
 | 
			
		||||
extern const size_t FLIPPER_SETTINGS_APPS_COUNT;
 | 
			
		||||
 | 
			
		||||
/* External Menu Apps list
 | 
			
		||||
 * Spawned by loader
 | 
			
		||||
 */
 | 
			
		||||
extern const FlipperExternalApplication FLIPPER_EXTERNAL_APPS[];
 | 
			
		||||
extern const size_t FLIPPER_EXTERNAL_APPS_COUNT;
 | 
			
		||||
 | 
			
		||||
@ -89,6 +89,14 @@ void cli_command_help(Cli* cli, FuriString* args, void* context) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void cli_command_uptime(Cli* cli, FuriString* args, void* context) {
 | 
			
		||||
    UNUSED(cli);
 | 
			
		||||
    UNUSED(args);
 | 
			
		||||
    UNUSED(context);
 | 
			
		||||
    uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency();
 | 
			
		||||
    printf("Uptime: %luh%lum%lus", uptime / 60 / 60, uptime / 60 % 60, uptime % 60);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void cli_command_date(Cli* cli, FuriString* args, void* context) {
 | 
			
		||||
    UNUSED(cli);
 | 
			
		||||
    UNUSED(context);
 | 
			
		||||
@ -165,24 +173,23 @@ void cli_command_log_tx_callback(const uint8_t* buffer, size_t size, void* conte
 | 
			
		||||
    furi_stream_buffer_send(context, buffer, size, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void cli_command_log_level_set_from_string(FuriString* level) {
 | 
			
		||||
    if(furi_string_cmpi_str(level, "default") == 0) {
 | 
			
		||||
        furi_log_set_level(FuriLogLevelDefault);
 | 
			
		||||
    } else if(furi_string_cmpi_str(level, "none") == 0) {
 | 
			
		||||
        furi_log_set_level(FuriLogLevelNone);
 | 
			
		||||
    } else if(furi_string_cmpi_str(level, "error") == 0) {
 | 
			
		||||
        furi_log_set_level(FuriLogLevelError);
 | 
			
		||||
    } else if(furi_string_cmpi_str(level, "warn") == 0) {
 | 
			
		||||
        furi_log_set_level(FuriLogLevelWarn);
 | 
			
		||||
    } else if(furi_string_cmpi_str(level, "info") == 0) {
 | 
			
		||||
        furi_log_set_level(FuriLogLevelInfo);
 | 
			
		||||
    } else if(furi_string_cmpi_str(level, "debug") == 0) {
 | 
			
		||||
        furi_log_set_level(FuriLogLevelDebug);
 | 
			
		||||
    } else if(furi_string_cmpi_str(level, "trace") == 0) {
 | 
			
		||||
        furi_log_set_level(FuriLogLevelTrace);
 | 
			
		||||
bool cli_command_log_level_set_from_string(FuriString* level) {
 | 
			
		||||
    FuriLogLevel log_level;
 | 
			
		||||
    if(furi_log_level_from_string(furi_string_get_cstr(level), &log_level)) {
 | 
			
		||||
        furi_log_set_level(log_level);
 | 
			
		||||
        return true;
 | 
			
		||||
    } else {
 | 
			
		||||
        printf("Unknown log level\r\n");
 | 
			
		||||
        printf("<log> — start logging using the current level from the system settings\r\n");
 | 
			
		||||
        printf("<log error> — only critical errors and other important messages\r\n");
 | 
			
		||||
        printf("<log warn> — non-critical errors and warnings including <log error>\r\n");
 | 
			
		||||
        printf("<log info> — non-critical information including <log warn>\r\n");
 | 
			
		||||
        printf("<log default> — the default system log level (equivalent to <log info>)\r\n");
 | 
			
		||||
        printf(
 | 
			
		||||
            "<log debug> — debug information including <log info> (may impact system performance)\r\n");
 | 
			
		||||
        printf(
 | 
			
		||||
            "<log trace> — system traces including <log debug> (may impact system performance)\r\n");
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void cli_command_log(Cli* cli, FuriString* args, void* context) {
 | 
			
		||||
@ -193,12 +200,20 @@ void cli_command_log(Cli* cli, FuriString* args, void* context) {
 | 
			
		||||
    bool restore_log_level = false;
 | 
			
		||||
 | 
			
		||||
    if(furi_string_size(args) > 0) {
 | 
			
		||||
        cli_command_log_level_set_from_string(args);
 | 
			
		||||
        if(!cli_command_log_level_set_from_string(args)) {
 | 
			
		||||
            furi_stream_buffer_free(ring);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        restore_log_level = true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const char* current_level;
 | 
			
		||||
    furi_log_level_to_string(furi_log_get_level(), ¤t_level);
 | 
			
		||||
    printf("Current log level: %s\r\n", current_level);
 | 
			
		||||
 | 
			
		||||
    furi_hal_console_set_tx_callback(cli_command_log_tx_callback, ring);
 | 
			
		||||
 | 
			
		||||
    printf("Use <log ?> to list available log levels\r\n");
 | 
			
		||||
    printf("Press CTRL+C to stop...\r\n");
 | 
			
		||||
    while(!cli_cmd_interrupt_received(cli)) {
 | 
			
		||||
        size_t ret = furi_stream_buffer_receive(ring, buffer, CLI_COMMAND_LOG_BUFFER_SIZE, 50);
 | 
			
		||||
@ -444,6 +459,7 @@ void cli_commands_init(Cli* cli) {
 | 
			
		||||
    cli_add_command(cli, "?", CliCommandFlagParallelSafe, cli_command_help, NULL);
 | 
			
		||||
    cli_add_command(cli, "help", CliCommandFlagParallelSafe, cli_command_help, NULL);
 | 
			
		||||
 | 
			
		||||
    cli_add_command(cli, "uptime", CliCommandFlagDefault, cli_command_uptime, NULL);
 | 
			
		||||
    cli_add_command(cli, "date", CliCommandFlagParallelSafe, cli_command_date, NULL);
 | 
			
		||||
    cli_add_command(cli, "log", CliCommandFlagParallelSafe, cli_command_log, NULL);
 | 
			
		||||
    cli_add_command(cli, "sysctl", CliCommandFlagDefault, cli_command_sysctl, NULL);
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@
 | 
			
		||||
#include <toolbox/saved_struct.h>
 | 
			
		||||
#include <storage/storage.h>
 | 
			
		||||
 | 
			
		||||
#define DESKTOP_SETTINGS_VER (7)
 | 
			
		||||
#define DESKTOP_SETTINGS_VER (8)
 | 
			
		||||
 | 
			
		||||
#define DESKTOP_SETTINGS_PATH INT_PATH(DESKTOP_SETTINGS_FILE_NAME)
 | 
			
		||||
#define DESKTOP_SETTINGS_MAGIC (0x17)
 | 
			
		||||
@ -42,7 +42,6 @@ typedef struct {
 | 
			
		||||
} PinCode;
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    bool is_external;
 | 
			
		||||
    char name_or_path[MAX_APP_LENGTH];
 | 
			
		||||
} FavoriteApp;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -14,8 +14,6 @@ void desktop_scene_debug_callback(DesktopEvent event, void* context) {
 | 
			
		||||
void desktop_scene_debug_on_enter(void* context) {
 | 
			
		||||
    Desktop* desktop = (Desktop*)context;
 | 
			
		||||
 | 
			
		||||
    desktop_debug_get_dolphin_data(desktop->debug_view);
 | 
			
		||||
 | 
			
		||||
    desktop_debug_set_callback(desktop->debug_view, desktop_scene_debug_callback, desktop);
 | 
			
		||||
    view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdDebug);
 | 
			
		||||
}
 | 
			
		||||
@ -32,24 +30,6 @@ bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
            dolphin_flush(dolphin);
 | 
			
		||||
            consumed = true;
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case DesktopDebugEventDeed:
 | 
			
		||||
            dolphin_deed(DolphinDeedTestRight);
 | 
			
		||||
            desktop_debug_get_dolphin_data(desktop->debug_view);
 | 
			
		||||
            consumed = true;
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case DesktopDebugEventWrongDeed:
 | 
			
		||||
            dolphin_deed(DolphinDeedTestLeft);
 | 
			
		||||
            desktop_debug_get_dolphin_data(desktop->debug_view);
 | 
			
		||||
            consumed = true;
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        case DesktopDebugEventSaveState:
 | 
			
		||||
            dolphin_flush(dolphin);
 | 
			
		||||
            consumed = true;
 | 
			
		||||
            break;
 | 
			
		||||
 | 
			
		||||
        default:
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
@ -60,6 +40,5 @@ bool desktop_scene_debug_on_event(void* context, SceneManagerEvent event) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void desktop_scene_debug_on_exit(void* context) {
 | 
			
		||||
    Desktop* desktop = (Desktop*)context;
 | 
			
		||||
    desktop_debug_reset_screen_idx(desktop->debug_view);
 | 
			
		||||
    UNUSED(context);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -18,20 +18,26 @@ void desktop_debug_set_callback(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void desktop_debug_render(Canvas* canvas, void* model) {
 | 
			
		||||
    UNUSED(model);
 | 
			
		||||
    canvas_clear(canvas);
 | 
			
		||||
    DesktopDebugViewModel* m = model;
 | 
			
		||||
    const Version* ver;
 | 
			
		||||
    char buffer[64];
 | 
			
		||||
 | 
			
		||||
    static const char* headers[] = {"Device Info:", "Dolphin Info:"};
 | 
			
		||||
 | 
			
		||||
    canvas_set_color(canvas, ColorBlack);
 | 
			
		||||
    canvas_set_font(canvas, FontPrimary);
 | 
			
		||||
    canvas_draw_str_aligned(
 | 
			
		||||
        canvas, 64, 1 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignTop, headers[m->screen]);
 | 
			
		||||
 | 
			
		||||
    uint32_t uptime = furi_get_tick() / furi_kernel_get_tick_frequency();
 | 
			
		||||
    snprintf(
 | 
			
		||||
        buffer,
 | 
			
		||||
        sizeof(buffer),
 | 
			
		||||
        "Uptime: %luh%lum%lus",
 | 
			
		||||
        uptime / 60 / 60,
 | 
			
		||||
        uptime / 60 % 60,
 | 
			
		||||
        uptime % 60);
 | 
			
		||||
    canvas_draw_str_aligned(canvas, 64, 1 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignTop, buffer);
 | 
			
		||||
 | 
			
		||||
    canvas_set_font(canvas, FontSecondary);
 | 
			
		||||
 | 
			
		||||
    if(m->screen != DesktopViewStatsMeta) {
 | 
			
		||||
    // Hardware version
 | 
			
		||||
    const char* my_name = furi_hal_version_get_name_ptr();
 | 
			
		||||
    snprintf(
 | 
			
		||||
@ -58,11 +64,7 @@ void desktop_debug_render(Canvas* canvas, void* model) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    snprintf(
 | 
			
		||||
            buffer,
 | 
			
		||||
            sizeof(buffer),
 | 
			
		||||
            "%s [%s]",
 | 
			
		||||
            version_get_version(ver),
 | 
			
		||||
            version_get_builddate(ver));
 | 
			
		||||
        buffer, sizeof(buffer), "%s [%s]", version_get_version(ver), version_get_builddate(ver));
 | 
			
		||||
    canvas_draw_str(canvas, 0, 30 + STATUS_BAR_Y_SHIFT, buffer);
 | 
			
		||||
 | 
			
		||||
    uint16_t api_major, api_minor;
 | 
			
		||||
@ -81,33 +83,6 @@ void desktop_debug_render(Canvas* canvas, void* model) {
 | 
			
		||||
    snprintf(
 | 
			
		||||
        buffer, sizeof(buffer), "[%d] %s", version_get_target(ver), version_get_gitbranch(ver));
 | 
			
		||||
    canvas_draw_str(canvas, 0, 50 + STATUS_BAR_Y_SHIFT, buffer);
 | 
			
		||||
 | 
			
		||||
    } else {
 | 
			
		||||
        Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
 | 
			
		||||
        DolphinStats stats = dolphin_stats(dolphin);
 | 
			
		||||
        furi_record_close(RECORD_DOLPHIN);
 | 
			
		||||
 | 
			
		||||
        uint32_t current_lvl = stats.level;
 | 
			
		||||
        uint32_t remaining = dolphin_state_xp_to_levelup(m->icounter);
 | 
			
		||||
 | 
			
		||||
        canvas_set_font(canvas, FontSecondary);
 | 
			
		||||
        snprintf(buffer, sizeof(buffer), "Icounter: %lu  Butthurt %lu", m->icounter, m->butthurt);
 | 
			
		||||
        canvas_draw_str(canvas, 5, 19 + STATUS_BAR_Y_SHIFT, buffer);
 | 
			
		||||
 | 
			
		||||
        snprintf(
 | 
			
		||||
            buffer,
 | 
			
		||||
            sizeof(buffer),
 | 
			
		||||
            "Level: %lu  To level up: %lu",
 | 
			
		||||
            current_lvl,
 | 
			
		||||
            (remaining == (uint32_t)(-1) ? remaining : 0));
 | 
			
		||||
        canvas_draw_str(canvas, 5, 29 + STATUS_BAR_Y_SHIFT, buffer);
 | 
			
		||||
 | 
			
		||||
        // even if timestamp is uint64_t, it's safe to cast it to uint32_t, because furi_hal_rtc_datetime_to_timestamp only returns uint32_t
 | 
			
		||||
        snprintf(buffer, sizeof(buffer), "%lu", (uint32_t)m->timestamp);
 | 
			
		||||
 | 
			
		||||
        canvas_draw_str(canvas, 5, 39 + STATUS_BAR_Y_SHIFT, buffer);
 | 
			
		||||
        canvas_draw_str(canvas, 0, 49 + STATUS_BAR_Y_SHIFT, "[< >] icounter value   [ok] save");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
View* desktop_debug_get_view(DesktopDebugView* debug_view) {
 | 
			
		||||
@ -115,61 +90,43 @@ View* desktop_debug_get_view(DesktopDebugView* debug_view) {
 | 
			
		||||
    return debug_view->view;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool desktop_debug_input(InputEvent* event, void* context) {
 | 
			
		||||
static bool desktop_debug_input(InputEvent* event, void* context) {
 | 
			
		||||
    furi_assert(event);
 | 
			
		||||
    furi_assert(context);
 | 
			
		||||
 | 
			
		||||
    DesktopDebugView* debug_view = context;
 | 
			
		||||
 | 
			
		||||
    if(event->type != InputTypeShort && event->type != InputTypeRepeat) {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    DesktopViewStatsScreens current = 0;
 | 
			
		||||
    with_view_model(
 | 
			
		||||
        debug_view->view,
 | 
			
		||||
        DesktopDebugViewModel * model,
 | 
			
		||||
        {
 | 
			
		||||
#ifdef SRV_DOLPHIN_STATE_DEBUG
 | 
			
		||||
            if((event->key == InputKeyDown) || (event->key == InputKeyUp)) {
 | 
			
		||||
                model->screen = !model->screen;
 | 
			
		||||
            }
 | 
			
		||||
#endif
 | 
			
		||||
            current = model->screen;
 | 
			
		||||
        },
 | 
			
		||||
        true);
 | 
			
		||||
 | 
			
		||||
    size_t count = (event->type == InputTypeRepeat) ? 10 : 1;
 | 
			
		||||
    if(current == DesktopViewStatsMeta) {
 | 
			
		||||
        if(event->key == InputKeyLeft) {
 | 
			
		||||
            while(count-- > 0) {
 | 
			
		||||
                debug_view->callback(DesktopDebugEventWrongDeed, debug_view->context);
 | 
			
		||||
            }
 | 
			
		||||
        } else if(event->key == InputKeyRight) {
 | 
			
		||||
            while(count-- > 0) {
 | 
			
		||||
                debug_view->callback(DesktopDebugEventDeed, debug_view->context);
 | 
			
		||||
            }
 | 
			
		||||
        } else if(event->key == InputKeyOk) {
 | 
			
		||||
            debug_view->callback(DesktopDebugEventSaveState, debug_view->context);
 | 
			
		||||
        } else {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if(event->key == InputKeyBack) {
 | 
			
		||||
    if(event->key == InputKeyBack && event->type == InputTypeShort) {
 | 
			
		||||
        debug_view->callback(DesktopDebugEventExit, debug_view->context);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void desktop_debug_enter(void* context) {
 | 
			
		||||
    DesktopDebugView* debug_view = context;
 | 
			
		||||
    furi_timer_start(debug_view->timer, furi_ms_to_ticks(1000));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void desktop_debug_exit(void* context) {
 | 
			
		||||
    DesktopDebugView* debug_view = context;
 | 
			
		||||
    furi_timer_stop(debug_view->timer);
 | 
			
		||||
}
 | 
			
		||||
void desktop_debug_timer(void* context) {
 | 
			
		||||
    DesktopDebugView* debug_view = context;
 | 
			
		||||
    view_get_model(debug_view->view);
 | 
			
		||||
    view_commit_model(debug_view->view, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DesktopDebugView* desktop_debug_alloc() {
 | 
			
		||||
    DesktopDebugView* debug_view = malloc(sizeof(DesktopDebugView));
 | 
			
		||||
    debug_view->view = view_alloc();
 | 
			
		||||
    view_allocate_model(debug_view->view, ViewModelTypeLocking, sizeof(DesktopDebugViewModel));
 | 
			
		||||
    debug_view->timer = furi_timer_alloc(desktop_debug_timer, FuriTimerTypePeriodic, debug_view);
 | 
			
		||||
    view_set_context(debug_view->view, debug_view);
 | 
			
		||||
    view_set_draw_callback(debug_view->view, (ViewDrawCallback)desktop_debug_render);
 | 
			
		||||
    view_set_input_callback(debug_view->view, desktop_debug_input);
 | 
			
		||||
    view_set_enter_callback(debug_view->view, desktop_debug_enter);
 | 
			
		||||
    view_set_exit_callback(debug_view->view, desktop_debug_exit);
 | 
			
		||||
 | 
			
		||||
    return debug_view;
 | 
			
		||||
}
 | 
			
		||||
@ -177,27 +134,7 @@ DesktopDebugView* desktop_debug_alloc() {
 | 
			
		||||
void desktop_debug_free(DesktopDebugView* debug_view) {
 | 
			
		||||
    furi_assert(debug_view);
 | 
			
		||||
 | 
			
		||||
    furi_timer_free(debug_view->timer);
 | 
			
		||||
    view_free(debug_view->view);
 | 
			
		||||
    free(debug_view);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void desktop_debug_get_dolphin_data(DesktopDebugView* debug_view) {
 | 
			
		||||
    Dolphin* dolphin = furi_record_open(RECORD_DOLPHIN);
 | 
			
		||||
    DolphinStats stats = dolphin_stats(dolphin);
 | 
			
		||||
    with_view_model(
 | 
			
		||||
        debug_view->view,
 | 
			
		||||
        DesktopDebugViewModel * model,
 | 
			
		||||
        {
 | 
			
		||||
            model->icounter = stats.icounter;
 | 
			
		||||
            model->butthurt = stats.butthurt;
 | 
			
		||||
            model->timestamp = stats.timestamp;
 | 
			
		||||
        },
 | 
			
		||||
        true);
 | 
			
		||||
 | 
			
		||||
    furi_record_close(RECORD_DOLPHIN);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void desktop_debug_reset_screen_idx(DesktopDebugView* debug_view) {
 | 
			
		||||
    with_view_model(
 | 
			
		||||
        debug_view->view, DesktopDebugViewModel * model, { model->screen = 0; }, true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -8,26 +8,13 @@ typedef struct DesktopDebugView DesktopDebugView;
 | 
			
		||||
 | 
			
		||||
typedef void (*DesktopDebugViewCallback)(DesktopEvent event, void* context);
 | 
			
		||||
 | 
			
		||||
// Debug info
 | 
			
		||||
typedef enum {
 | 
			
		||||
    DesktopViewStatsFw,
 | 
			
		||||
    DesktopViewStatsMeta,
 | 
			
		||||
    DesktopViewStatsTotalCount,
 | 
			
		||||
} DesktopViewStatsScreens;
 | 
			
		||||
 | 
			
		||||
struct DesktopDebugView {
 | 
			
		||||
    View* view;
 | 
			
		||||
    FuriTimer* timer;
 | 
			
		||||
    DesktopDebugViewCallback callback;
 | 
			
		||||
    void* context;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    uint32_t icounter;
 | 
			
		||||
    uint32_t butthurt;
 | 
			
		||||
    uint64_t timestamp;
 | 
			
		||||
    DesktopViewStatsScreens screen;
 | 
			
		||||
} DesktopDebugViewModel;
 | 
			
		||||
 | 
			
		||||
void desktop_debug_set_callback(
 | 
			
		||||
    DesktopDebugView* debug_view,
 | 
			
		||||
    DesktopDebugViewCallback callback,
 | 
			
		||||
@ -36,7 +23,5 @@ void desktop_debug_set_callback(
 | 
			
		||||
View* desktop_debug_get_view(DesktopDebugView* debug_view);
 | 
			
		||||
 | 
			
		||||
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_free(DesktopDebugView* debug_view);
 | 
			
		||||
 | 
			
		||||
@ -303,6 +303,12 @@ static bool browser_is_list_load_required(FileBrowserModel* model) {
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void browser_list_rollover(FileBrowserModel* model) {
 | 
			
		||||
    if(!model->list_loading && items_array_size(model->items) < model->item_cnt) {
 | 
			
		||||
        items_array_reset(model->items);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void browser_update_offset(FileBrowser* browser) {
 | 
			
		||||
    furi_assert(browser);
 | 
			
		||||
 | 
			
		||||
@ -385,7 +391,7 @@ static void browser_list_load_cb(void* context, uint32_t list_load_offset) {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        true);
 | 
			
		||||
        false);
 | 
			
		||||
 | 
			
		||||
    BrowserItem_t_clear(&back_item);
 | 
			
		||||
}
 | 
			
		||||
@ -425,14 +431,15 @@ static void
 | 
			
		||||
                (browser->hide_ext) && (item.type == BrowserItemTypeFile));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // We shouldn't update screen on each item if custom callback is not set
 | 
			
		||||
        // Otherwise it will cause screen flickering
 | 
			
		||||
        bool instant_update = (browser->item_callback != NULL);
 | 
			
		||||
        with_view_model(
 | 
			
		||||
            browser->view,
 | 
			
		||||
            FileBrowserModel * model,
 | 
			
		||||
            {
 | 
			
		||||
                items_array_push_back(model->items, item);
 | 
			
		||||
                // TODO: calculate if element is visible
 | 
			
		||||
            },
 | 
			
		||||
            true);
 | 
			
		||||
            { items_array_push_back(model->items, item); },
 | 
			
		||||
            instant_update);
 | 
			
		||||
 | 
			
		||||
        furi_string_free(item.display_name);
 | 
			
		||||
        furi_string_free(item.path);
 | 
			
		||||
        if(item.custom_icon_data) {
 | 
			
		||||
@ -440,7 +447,18 @@ static void
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        with_view_model(
 | 
			
		||||
            browser->view, FileBrowserModel * model, { model->list_loading = false; }, true);
 | 
			
		||||
            browser->view,
 | 
			
		||||
            FileBrowserModel * model,
 | 
			
		||||
            {
 | 
			
		||||
                model->list_loading = false;
 | 
			
		||||
                if(browser_is_list_load_required(model)) {
 | 
			
		||||
                    model->list_loading = true;
 | 
			
		||||
                    int32_t load_offset = CLAMP(
 | 
			
		||||
                        model->item_idx - ITEM_LIST_LEN_MAX / 2, (int32_t)model->item_cnt, 0);
 | 
			
		||||
                    file_browser_worker_load(browser->worker, load_offset, ITEM_LIST_LEN_MAX);
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            true);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -604,11 +622,13 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) {
 | 
			
		||||
                        if(model->item_idx < scroll_speed) {
 | 
			
		||||
                            model->button_held_for_ticks = 0;
 | 
			
		||||
                            model->item_idx = model->item_cnt - 1;
 | 
			
		||||
                            browser_list_rollover(model);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            model->item_idx =
 | 
			
		||||
                                ((model->item_idx - scroll_speed) + model->item_cnt) %
 | 
			
		||||
                                model->item_cnt;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if(browser_is_list_load_required(model)) {
 | 
			
		||||
                            model->list_loading = true;
 | 
			
		||||
                            int32_t load_offset = CLAMP(
 | 
			
		||||
@ -622,13 +642,14 @@ static bool file_browser_view_input_callback(InputEvent* event, void* context) {
 | 
			
		||||
 | 
			
		||||
                        model->button_held_for_ticks += 1;
 | 
			
		||||
                    } else if(event->key == InputKeyDown) {
 | 
			
		||||
                        int32_t count = model->item_cnt;
 | 
			
		||||
                        if(model->item_idx + scroll_speed >= count) {
 | 
			
		||||
                        if(model->item_idx + scroll_speed >= (int32_t)model->item_cnt) {
 | 
			
		||||
                            model->button_held_for_ticks = 0;
 | 
			
		||||
                            model->item_idx = 0;
 | 
			
		||||
                            browser_list_rollover(model);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            model->item_idx = (model->item_idx + scroll_speed) % model->item_cnt;
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        if(browser_is_list_load_required(model)) {
 | 
			
		||||
                            model->list_loading = true;
 | 
			
		||||
                            int32_t load_offset = CLAMP(
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@
 | 
			
		||||
#include <applications.h>
 | 
			
		||||
#include <storage/storage.h>
 | 
			
		||||
#include <furi_hal.h>
 | 
			
		||||
#include <assets_icons.h>
 | 
			
		||||
 | 
			
		||||
#include <dialogs/dialogs.h>
 | 
			
		||||
#include <toolbox/path.h>
 | 
			
		||||
@ -11,7 +12,20 @@
 | 
			
		||||
 | 
			
		||||
#define TAG "Loader"
 | 
			
		||||
#define LOADER_MAGIC_THREAD_VALUE 0xDEADBEEF
 | 
			
		||||
// api
 | 
			
		||||
 | 
			
		||||
// helpers
 | 
			
		||||
 | 
			
		||||
static const char* loader_find_external_application_by_name(const char* app_name) {
 | 
			
		||||
    for(size_t i = 0; i < FLIPPER_EXTERNAL_APPS_COUNT; i++) {
 | 
			
		||||
        if(strcmp(FLIPPER_EXTERNAL_APPS[i].name, app_name) == 0) {
 | 
			
		||||
            return FLIPPER_EXTERNAL_APPS[i].path;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// API
 | 
			
		||||
 | 
			
		||||
LoaderStatus
 | 
			
		||||
    loader_start(Loader* loader, const char* name, const char* args, FuriString* error_message) {
 | 
			
		||||
@ -33,17 +47,33 @@ LoaderStatus loader_start_with_gui_error(Loader* loader, const char* name, const
 | 
			
		||||
    FuriString* error_message = furi_string_alloc();
 | 
			
		||||
    LoaderStatus status = loader_start(loader, name, args, error_message);
 | 
			
		||||
 | 
			
		||||
    if(status == LoaderStatusErrorUnknownApp &&
 | 
			
		||||
       loader_find_external_application_by_name(name) != NULL) {
 | 
			
		||||
        // Special case for external apps
 | 
			
		||||
        DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
 | 
			
		||||
        DialogMessage* message = dialog_message_alloc();
 | 
			
		||||
        dialog_message_set_header(message, "Update needed", 64, 3, AlignCenter, AlignTop);
 | 
			
		||||
        dialog_message_set_buttons(message, NULL, NULL, NULL);
 | 
			
		||||
        dialog_message_set_icon(message, &I_DolphinCommon_56x48, 72, 17);
 | 
			
		||||
        dialog_message_set_text(
 | 
			
		||||
            message, "Update firmware\nto run this app", 3, 26, AlignLeft, AlignTop);
 | 
			
		||||
        dialog_message_show(dialogs, message);
 | 
			
		||||
        dialog_message_free(message);
 | 
			
		||||
        furi_record_close(RECORD_DIALOGS);
 | 
			
		||||
    } else if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) {
 | 
			
		||||
        // TODO: we have many places where we can emit a double start, ex: desktop, menu
 | 
			
		||||
        // so i prefer to not show LoaderStatusErrorAppStarted error message for now
 | 
			
		||||
    if(status == LoaderStatusErrorUnknownApp || status == LoaderStatusErrorInternal) {
 | 
			
		||||
        DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
 | 
			
		||||
        DialogMessage* message = dialog_message_alloc();
 | 
			
		||||
        dialog_message_set_header(message, "Error", 64, 0, AlignCenter, AlignTop);
 | 
			
		||||
        dialog_message_set_buttons(message, NULL, NULL, NULL);
 | 
			
		||||
 | 
			
		||||
        furi_string_replace(error_message, ":", "\n");
 | 
			
		||||
        furi_string_replace(error_message, "/ext/apps/", "");
 | 
			
		||||
        furi_string_replace(error_message, ", ", "\n");
 | 
			
		||||
        furi_string_replace(error_message, ": ", "\n");
 | 
			
		||||
 | 
			
		||||
        dialog_message_set_text(
 | 
			
		||||
            message, furi_string_get_cstr(error_message), 64, 32, AlignCenter, AlignCenter);
 | 
			
		||||
            message, furi_string_get_cstr(error_message), 64, 35, AlignCenter, AlignCenter);
 | 
			
		||||
 | 
			
		||||
        dialog_message_show(dialogs, message);
 | 
			
		||||
        dialog_message_free(message);
 | 
			
		||||
@ -268,22 +298,20 @@ static LoaderStatus loader_start_external_app(
 | 
			
		||||
        if(preload_res != FlipperApplicationPreloadStatusSuccess) {
 | 
			
		||||
            const char* err_msg = flipper_application_preload_status_to_string(preload_res);
 | 
			
		||||
            status = loader_make_status_error(
 | 
			
		||||
                LoaderStatusErrorInternal, error_message, "Preload failed %s: %s", path, err_msg);
 | 
			
		||||
                LoaderStatusErrorInternal, error_message, "Preload failed, %s: %s", path, err_msg);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        FURI_LOG_I(TAG, "Mapping");
 | 
			
		||||
        FlipperApplicationLoadStatus load_status =
 | 
			
		||||
            flipper_application_map_to_memory(loader->app.fap);
 | 
			
		||||
        if(load_status != FlipperApplicationLoadStatusSuccess) {
 | 
			
		||||
            const char* err_msg = flipper_application_load_status_to_string(load_status);
 | 
			
		||||
            status = loader_make_status_error(
 | 
			
		||||
                LoaderStatusErrorInternal, error_message, "Load failed %s: %s", path, err_msg);
 | 
			
		||||
                LoaderStatusErrorInternal, error_message, "Load failed, %s: %s", path, err_msg);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        FURI_LOG_I(TAG, "Loaded in %zums", (size_t)(furi_get_tick() - start));
 | 
			
		||||
        FURI_LOG_I(TAG, "Starting app");
 | 
			
		||||
 | 
			
		||||
        loader->app.thread = flipper_application_alloc_thread(loader->app.fap, args);
 | 
			
		||||
        FuriString* app_name = furi_string_alloc();
 | 
			
		||||
@ -379,7 +407,15 @@ static LoaderStatus loader_do_start_by_name(
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // check external apps
 | 
			
		||||
        // check External Applications
 | 
			
		||||
        {
 | 
			
		||||
            const char* path = loader_find_external_application_by_name(name);
 | 
			
		||||
            if(path) {
 | 
			
		||||
                name = path;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // check Faps
 | 
			
		||||
        {
 | 
			
		||||
            Storage* storage = furi_record_open(RECORD_STORAGE);
 | 
			
		||||
            if(storage_file_exists(storage, name)) {
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ static int32_t loader_applications_thread(void* p);
 | 
			
		||||
LoaderApplications* loader_applications_alloc(void (*closed_cb)(void*), void* context) {
 | 
			
		||||
    LoaderApplications* loader_applications = malloc(sizeof(LoaderApplications));
 | 
			
		||||
    loader_applications->thread =
 | 
			
		||||
        furi_thread_alloc_ex(TAG, 512, loader_applications_thread, (void*)loader_applications);
 | 
			
		||||
        furi_thread_alloc_ex(TAG, 768, loader_applications_thread, (void*)loader_applications);
 | 
			
		||||
    loader_applications->closed_cb = closed_cb;
 | 
			
		||||
    loader_applications->context = context;
 | 
			
		||||
    furi_thread_start(loader_applications->thread);
 | 
			
		||||
@ -38,6 +38,11 @@ typedef struct {
 | 
			
		||||
    FuriString* fap_path;
 | 
			
		||||
    DialogsApp* dialogs;
 | 
			
		||||
    Storage* storage;
 | 
			
		||||
    Loader* loader;
 | 
			
		||||
 | 
			
		||||
    Gui* gui;
 | 
			
		||||
    ViewHolder* view_holder;
 | 
			
		||||
    Loading* loading;
 | 
			
		||||
} LoaderApplicationsApp;
 | 
			
		||||
 | 
			
		||||
static LoaderApplicationsApp* loader_applications_app_alloc() {
 | 
			
		||||
@ -45,15 +50,30 @@ static LoaderApplicationsApp* loader_applications_app_alloc() {
 | 
			
		||||
    app->fap_path = furi_string_alloc_set(EXT_PATH("apps"));
 | 
			
		||||
    app->dialogs = furi_record_open(RECORD_DIALOGS);
 | 
			
		||||
    app->storage = furi_record_open(RECORD_STORAGE);
 | 
			
		||||
    app->loader = furi_record_open(RECORD_LOADER);
 | 
			
		||||
 | 
			
		||||
    app->gui = furi_record_open(RECORD_GUI);
 | 
			
		||||
    app->view_holder = view_holder_alloc();
 | 
			
		||||
    app->loading = loading_alloc();
 | 
			
		||||
 | 
			
		||||
    view_holder_attach_to_gui(app->view_holder, app->gui);
 | 
			
		||||
    view_holder_set_view(app->view_holder, loading_get_view(app->loading));
 | 
			
		||||
 | 
			
		||||
    return app;
 | 
			
		||||
} //-V773
 | 
			
		||||
 | 
			
		||||
static void loader_applications_app_free(LoaderApplicationsApp* loader_applications_app) {
 | 
			
		||||
    furi_assert(loader_applications_app);
 | 
			
		||||
static void loader_applications_app_free(LoaderApplicationsApp* app) {
 | 
			
		||||
    furi_assert(app);
 | 
			
		||||
 | 
			
		||||
    view_holder_free(app->view_holder);
 | 
			
		||||
    loading_free(app->loading);
 | 
			
		||||
    furi_record_close(RECORD_GUI);
 | 
			
		||||
 | 
			
		||||
    furi_record_close(RECORD_LOADER);
 | 
			
		||||
    furi_record_close(RECORD_DIALOGS);
 | 
			
		||||
    furi_record_close(RECORD_STORAGE);
 | 
			
		||||
    furi_string_free(loader_applications_app->fap_path);
 | 
			
		||||
    free(loader_applications_app);
 | 
			
		||||
    furi_string_free(app->fap_path);
 | 
			
		||||
    free(app);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool loader_applications_item_callback(
 | 
			
		||||
@ -96,47 +116,38 @@ static void loader_pubsub_callback(const void* message, void* context) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void loader_applications_start_app(const char* name) {
 | 
			
		||||
    // start loading animation
 | 
			
		||||
    Gui* gui = furi_record_open(RECORD_GUI);
 | 
			
		||||
    ViewHolder* view_holder = view_holder_alloc();
 | 
			
		||||
    Loading* loading = loading_alloc();
 | 
			
		||||
 | 
			
		||||
    view_holder_attach_to_gui(view_holder, gui);
 | 
			
		||||
    view_holder_set_view(view_holder, loading_get_view(loading));
 | 
			
		||||
    view_holder_start(view_holder);
 | 
			
		||||
static void loader_applications_start_app(LoaderApplicationsApp* app) {
 | 
			
		||||
    const char* name = furi_string_get_cstr(app->fap_path);
 | 
			
		||||
 | 
			
		||||
    // load app
 | 
			
		||||
    FuriThreadId thread_id = furi_thread_get_current_id();
 | 
			
		||||
    Loader* loader = furi_record_open(RECORD_LOADER);
 | 
			
		||||
    FuriPubSubSubscription* subscription =
 | 
			
		||||
        furi_pubsub_subscribe(loader_get_pubsub(loader), loader_pubsub_callback, thread_id);
 | 
			
		||||
        furi_pubsub_subscribe(loader_get_pubsub(app->loader), loader_pubsub_callback, thread_id);
 | 
			
		||||
 | 
			
		||||
    LoaderStatus status = loader_start_with_gui_error(loader, name, NULL);
 | 
			
		||||
    LoaderStatus status = loader_start_with_gui_error(app->loader, name, NULL);
 | 
			
		||||
 | 
			
		||||
    if(status == LoaderStatusOk) {
 | 
			
		||||
        furi_thread_flags_wait(APPLICATION_STOP_EVENT, FuriFlagWaitAny, FuriWaitForever);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    furi_pubsub_unsubscribe(loader_get_pubsub(loader), subscription);
 | 
			
		||||
    furi_record_close(RECORD_LOADER);
 | 
			
		||||
 | 
			
		||||
    // stop loading animation
 | 
			
		||||
    view_holder_stop(view_holder);
 | 
			
		||||
    view_holder_free(view_holder);
 | 
			
		||||
    loading_free(loading);
 | 
			
		||||
    furi_record_close(RECORD_GUI);
 | 
			
		||||
    furi_pubsub_unsubscribe(loader_get_pubsub(app->loader), subscription);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int32_t loader_applications_thread(void* p) {
 | 
			
		||||
    LoaderApplications* loader_applications = p;
 | 
			
		||||
    LoaderApplicationsApp* loader_applications_app = loader_applications_app_alloc();
 | 
			
		||||
    LoaderApplicationsApp* app = loader_applications_app_alloc();
 | 
			
		||||
 | 
			
		||||
    while(loader_applications_select_app(loader_applications_app)) {
 | 
			
		||||
        loader_applications_start_app(furi_string_get_cstr(loader_applications_app->fap_path));
 | 
			
		||||
    // start loading animation
 | 
			
		||||
    view_holder_start(app->view_holder);
 | 
			
		||||
 | 
			
		||||
    while(loader_applications_select_app(app)) {
 | 
			
		||||
        loader_applications_start_app(app);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    loader_applications_app_free(loader_applications_app);
 | 
			
		||||
    // stop loading animation
 | 
			
		||||
    view_holder_stop(app->view_holder);
 | 
			
		||||
 | 
			
		||||
    loader_applications_app_free(app);
 | 
			
		||||
 | 
			
		||||
    if(loader_applications->closed_cb) {
 | 
			
		||||
        loader_applications->closed_cb(loader_applications->context);
 | 
			
		||||
 | 
			
		||||
@ -52,12 +52,18 @@ static void loader_menu_start(const char* name) {
 | 
			
		||||
    furi_record_close(RECORD_LOADER);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void loader_menu_callback(void* context, uint32_t index) {
 | 
			
		||||
static void loader_menu_apps_callback(void* context, uint32_t index) {
 | 
			
		||||
    UNUSED(context);
 | 
			
		||||
    const char* name = FLIPPER_APPS[index].name;
 | 
			
		||||
    loader_menu_start(name);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void loader_menu_external_apps_callback(void* context, uint32_t index) {
 | 
			
		||||
    UNUSED(context);
 | 
			
		||||
    const char* path = FLIPPER_EXTERNAL_APPS[index].name;
 | 
			
		||||
    loader_menu_start(path);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void loader_menu_applications_callback(void* context, uint32_t index) {
 | 
			
		||||
    UNUSED(index);
 | 
			
		||||
    UNUSED(context);
 | 
			
		||||
@ -89,13 +95,24 @@ static uint32_t loader_menu_exit(void* context) {
 | 
			
		||||
 | 
			
		||||
static void loader_menu_build_menu(LoaderMenuApp* app, LoaderMenu* menu) {
 | 
			
		||||
    size_t i;
 | 
			
		||||
 | 
			
		||||
    for(i = 0; i < FLIPPER_EXTERNAL_APPS_COUNT; i++) {
 | 
			
		||||
        menu_add_item(
 | 
			
		||||
            app->primary_menu,
 | 
			
		||||
            FLIPPER_EXTERNAL_APPS[i].name,
 | 
			
		||||
            FLIPPER_EXTERNAL_APPS[i].icon,
 | 
			
		||||
            i,
 | 
			
		||||
            loader_menu_external_apps_callback,
 | 
			
		||||
            (void*)menu);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for(i = 0; i < FLIPPER_APPS_COUNT; i++) {
 | 
			
		||||
        menu_add_item(
 | 
			
		||||
            app->primary_menu,
 | 
			
		||||
            FLIPPER_APPS[i].name,
 | 
			
		||||
            FLIPPER_APPS[i].icon,
 | 
			
		||||
            i,
 | 
			
		||||
            loader_menu_callback,
 | 
			
		||||
            loader_menu_apps_callback,
 | 
			
		||||
            (void*)menu);
 | 
			
		||||
    }
 | 
			
		||||
    menu_add_item(
 | 
			
		||||
 | 
			
		||||
@ -117,6 +117,7 @@ static bool power_update_info(Power* power) {
 | 
			
		||||
 | 
			
		||||
    info.is_charging = furi_hal_power_is_charging();
 | 
			
		||||
    info.gauge_is_ok = furi_hal_power_gauge_is_ok();
 | 
			
		||||
    info.is_shutdown_requested = furi_hal_power_is_shutdown_requested();
 | 
			
		||||
    info.charge = furi_hal_power_get_pct();
 | 
			
		||||
    info.health = furi_hal_power_get_bat_health_pct();
 | 
			
		||||
    info.capacity_remaining = furi_hal_power_get_battery_remaining_capacity();
 | 
			
		||||
@ -145,7 +146,7 @@ static void power_check_low_battery(Power* power) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Check battery charge and vbus voltage
 | 
			
		||||
    if((power->info.charge == 0) && (power->info.voltage_vbus < 4.0f) &&
 | 
			
		||||
    if((power->info.is_shutdown_requested) && (power->info.voltage_vbus < 4.0f) &&
 | 
			
		||||
       power->show_low_bat_level_message) {
 | 
			
		||||
        if(!power->battery_low) {
 | 
			
		||||
            view_dispatcher_send_to_front(power->view_dispatcher);
 | 
			
		||||
 | 
			
		||||
@ -37,6 +37,7 @@ typedef struct {
 | 
			
		||||
typedef struct {
 | 
			
		||||
    bool gauge_is_ok;
 | 
			
		||||
    bool is_charging;
 | 
			
		||||
    bool is_shutdown_requested;
 | 
			
		||||
 | 
			
		||||
    float current_charger;
 | 
			
		||||
    float current_gauge;
 | 
			
		||||
 | 
			
		||||
@ -430,6 +430,20 @@ FS_Error storage_common_rename(Storage* storage, const char* old_path, const cha
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(storage_dir_exists(storage, old_path)) {
 | 
			
		||||
            FuriString* dir_path = furi_string_alloc_set_str(old_path);
 | 
			
		||||
            if(!furi_string_end_with_str(dir_path, "/")) {
 | 
			
		||||
                furi_string_cat_str(dir_path, "/");
 | 
			
		||||
            }
 | 
			
		||||
            const char* dir_path_s = furi_string_get_cstr(dir_path);
 | 
			
		||||
            if(strncmp(new_path, dir_path_s, strlen(dir_path_s)) == 0) {
 | 
			
		||||
                error = FSE_INVALID_NAME;
 | 
			
		||||
                furi_string_free(dir_path);
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
            furi_string_free(dir_path);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(storage_file_exists(storage, new_path)) {
 | 
			
		||||
            storage_common_remove(storage, new_path);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -82,7 +82,9 @@ static DialogMessageButton icon1_screen(DialogsApp* dialogs, DialogMessage* mess
 | 
			
		||||
static DialogMessageButton icon2_screen(DialogsApp* dialogs, DialogMessage* message) {
 | 
			
		||||
    DialogMessageButton result;
 | 
			
		||||
 | 
			
		||||
    dialog_message_set_icon(message, &I_Certification2_98x33, 15, 10);
 | 
			
		||||
    dialog_message_set_icon(message, &I_Certification2_46x33, 15, 10);
 | 
			
		||||
    dialog_message_set_text(
 | 
			
		||||
        message, furi_hal_version_get_mic_id(), 63, 27, AlignLeft, AlignCenter);
 | 
			
		||||
    result = dialog_message_show(dialogs, message);
 | 
			
		||||
    dialog_message_set_icon(message, NULL, 0, 0);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -5,11 +5,26 @@
 | 
			
		||||
#include <storage/storage.h>
 | 
			
		||||
#include <dialogs/dialogs.h>
 | 
			
		||||
 | 
			
		||||
#define APPS_COUNT (FLIPPER_APPS_COUNT + FLIPPER_EXTERNAL_APPS_COUNT)
 | 
			
		||||
 | 
			
		||||
#define EXTERNAL_BROWSER_NAME ("Applications")
 | 
			
		||||
#define EXTERNAL_BROWSER_INDEX (FLIPPER_APPS_COUNT + 1)
 | 
			
		||||
#define EXTERNAL_BROWSER_INDEX (APPS_COUNT + 1)
 | 
			
		||||
 | 
			
		||||
#define EXTERNAL_APPLICATION_NAME ("[External Application]")
 | 
			
		||||
#define EXTERNAL_APPLICATION_INDEX (FLIPPER_APPS_COUNT + 2)
 | 
			
		||||
#define EXTERNAL_APPLICATION_INDEX (APPS_COUNT + 2)
 | 
			
		||||
 | 
			
		||||
#define PRESELECTED_SPECIAL 0xffffffff
 | 
			
		||||
 | 
			
		||||
static const char* favorite_fap_get_app_name(size_t i) {
 | 
			
		||||
    const char* name;
 | 
			
		||||
    if(i < FLIPPER_APPS_COUNT) {
 | 
			
		||||
        name = FLIPPER_APPS[i].name;
 | 
			
		||||
    } else {
 | 
			
		||||
        name = FLIPPER_EXTERNAL_APPS[i - FLIPPER_APPS_COUNT].name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return name;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool favorite_fap_selector_item_callback(
 | 
			
		||||
    FuriString* file_path,
 | 
			
		||||
@ -42,21 +57,17 @@ void desktop_settings_scene_favorite_on_enter(void* context) {
 | 
			
		||||
 | 
			
		||||
    uint32_t primary_favorite =
 | 
			
		||||
        scene_manager_get_scene_state(app->scene_manager, DesktopSettingsAppSceneFavorite);
 | 
			
		||||
    uint32_t pre_select_item = 0;
 | 
			
		||||
    uint32_t pre_select_item = PRESELECTED_SPECIAL;
 | 
			
		||||
    FavoriteApp* curr_favorite_app = primary_favorite ? &app->settings.favorite_primary :
 | 
			
		||||
                                                        &app->settings.favorite_secondary;
 | 
			
		||||
 | 
			
		||||
    for(size_t i = 0; i < FLIPPER_APPS_COUNT; i++) {
 | 
			
		||||
        submenu_add_item(
 | 
			
		||||
            submenu,
 | 
			
		||||
            FLIPPER_APPS[i].name,
 | 
			
		||||
            i,
 | 
			
		||||
            desktop_settings_scene_favorite_submenu_callback,
 | 
			
		||||
            app);
 | 
			
		||||
    for(size_t i = 0; i < APPS_COUNT; i++) {
 | 
			
		||||
        const char* name = favorite_fap_get_app_name(i);
 | 
			
		||||
 | 
			
		||||
        submenu_add_item(submenu, name, i, desktop_settings_scene_favorite_submenu_callback, app);
 | 
			
		||||
 | 
			
		||||
        // Select favorite item in submenu
 | 
			
		||||
        if(!curr_favorite_app->is_external &&
 | 
			
		||||
           !strcmp(FLIPPER_APPS[i].name, curr_favorite_app->name_or_path)) {
 | 
			
		||||
        if(!strcmp(name, curr_favorite_app->name_or_path)) {
 | 
			
		||||
            pre_select_item = i;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@ -77,7 +88,7 @@ void desktop_settings_scene_favorite_on_enter(void* context) {
 | 
			
		||||
        desktop_settings_scene_favorite_submenu_callback,
 | 
			
		||||
        app);
 | 
			
		||||
 | 
			
		||||
    if(curr_favorite_app->is_external) {
 | 
			
		||||
    if(pre_select_item == PRESELECTED_SPECIAL) {
 | 
			
		||||
        if(curr_favorite_app->name_or_path[0] == '\0') {
 | 
			
		||||
            pre_select_item = EXTERNAL_BROWSER_INDEX;
 | 
			
		||||
        } else {
 | 
			
		||||
@ -104,7 +115,6 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e
 | 
			
		||||
 | 
			
		||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
			
		||||
        if(event.event == EXTERNAL_BROWSER_INDEX) {
 | 
			
		||||
            curr_favorite_app->is_external = true;
 | 
			
		||||
            curr_favorite_app->name_or_path[0] = '\0';
 | 
			
		||||
            consumed = true;
 | 
			
		||||
        } else if(event.event == EXTERNAL_APPLICATION_INDEX) {
 | 
			
		||||
@ -125,7 +135,6 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e
 | 
			
		||||
 | 
			
		||||
            if(dialog_file_browser_show(app->dialogs, temp_path, temp_path, &browser_options)) {
 | 
			
		||||
                submenu_reset(app->submenu); // Prevent menu from being shown when we exiting scene
 | 
			
		||||
                curr_favorite_app->is_external = true;
 | 
			
		||||
                strncpy(
 | 
			
		||||
                    curr_favorite_app->name_or_path,
 | 
			
		||||
                    furi_string_get_cstr(temp_path),
 | 
			
		||||
@ -133,9 +142,8 @@ bool desktop_settings_scene_favorite_on_event(void* context, SceneManagerEvent e
 | 
			
		||||
                consumed = true;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            curr_favorite_app->is_external = false;
 | 
			
		||||
            strncpy(
 | 
			
		||||
                curr_favorite_app->name_or_path, FLIPPER_APPS[event.event].name, MAX_APP_LENGTH);
 | 
			
		||||
            const char* name = favorite_fap_get_app_name(event.event);
 | 
			
		||||
            if(name) strncpy(curr_favorite_app->name_or_path, name, MAX_APP_LENGTH);
 | 
			
		||||
            consumed = true;
 | 
			
		||||
        }
 | 
			
		||||
        if(consumed) {
 | 
			
		||||
 | 
			
		||||
@ -54,8 +54,7 @@ static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) {
 | 
			
		||||
            (uint32_t)(data->vbus_voltage * 10) % 10,
 | 
			
		||||
            current);
 | 
			
		||||
    } else if(current < -5) {
 | 
			
		||||
        // Often gauge reports anything in the range 1~5ma as 5ma
 | 
			
		||||
        // That brings confusion, so we'll treat it as Napping
 | 
			
		||||
        // 0-5ma deadband
 | 
			
		||||
        snprintf(
 | 
			
		||||
            emote,
 | 
			
		||||
            sizeof(emote),
 | 
			
		||||
 | 
			
		||||
@ -19,7 +19,7 @@ static const char* update_task_stage_descr[] = {
 | 
			
		||||
    [UpdateTaskStageRadioErase] = "Uninstalling radio FW",
 | 
			
		||||
    [UpdateTaskStageRadioWrite] = "Writing radio FW",
 | 
			
		||||
    [UpdateTaskStageRadioInstall] = "Installing radio FW",
 | 
			
		||||
    [UpdateTaskStageRadioBusy] = "Radio is updating",
 | 
			
		||||
    [UpdateTaskStageRadioBusy] = "Core 2 busy",
 | 
			
		||||
    [UpdateTaskStageOBValidation] = "Validating opt. bytes",
 | 
			
		||||
    [UpdateTaskStageLfsBackup] = "Backing up LFS",
 | 
			
		||||
    [UpdateTaskStageLfsRestore] = "Restoring LFS",
 | 
			
		||||
@ -30,6 +30,191 @@ static const char* update_task_stage_descr[] = {
 | 
			
		||||
    [UpdateTaskStageOBError] = "OB, report",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const struct {
 | 
			
		||||
    UpdateTaskStage stage;
 | 
			
		||||
    uint8_t percent_min, percent_max;
 | 
			
		||||
    const char* descr;
 | 
			
		||||
} update_task_error_detail[] = {
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageReadManifest,
 | 
			
		||||
        .percent_min = 0,
 | 
			
		||||
        .percent_max = 13,
 | 
			
		||||
        .descr = "Wrong Updater HW",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageReadManifest,
 | 
			
		||||
        .percent_min = 14,
 | 
			
		||||
        .percent_max = 20,
 | 
			
		||||
        .descr = "Manifest pointer error",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageReadManifest,
 | 
			
		||||
        .percent_min = 21,
 | 
			
		||||
        .percent_max = 30,
 | 
			
		||||
        .descr = "Manifest load error",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageReadManifest,
 | 
			
		||||
        .percent_min = 31,
 | 
			
		||||
        .percent_max = 40,
 | 
			
		||||
        .descr = "Wrong package version",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageReadManifest,
 | 
			
		||||
        .percent_min = 41,
 | 
			
		||||
        .percent_max = 50,
 | 
			
		||||
        .descr = "HW Target mismatch",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageReadManifest,
 | 
			
		||||
        .percent_min = 51,
 | 
			
		||||
        .percent_max = 60,
 | 
			
		||||
        .descr = "No DFU file",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageReadManifest,
 | 
			
		||||
        .percent_min = 61,
 | 
			
		||||
        .percent_max = 80,
 | 
			
		||||
        .descr = "No Radio file",
 | 
			
		||||
    },
 | 
			
		||||
#ifndef FURI_RAM_EXEC
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageLfsBackup,
 | 
			
		||||
        .percent_min = 0,
 | 
			
		||||
        .percent_max = 100,
 | 
			
		||||
        .descr = "FS R/W error",
 | 
			
		||||
    },
 | 
			
		||||
#else
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageRadioImageValidate,
 | 
			
		||||
        .percent_min = 0,
 | 
			
		||||
        .percent_max = 98,
 | 
			
		||||
        .descr = "FS Read error",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageRadioImageValidate,
 | 
			
		||||
        .percent_min = 99,
 | 
			
		||||
        .percent_max = 100,
 | 
			
		||||
        .descr = "CRC mismatch",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageRadioErase,
 | 
			
		||||
        .percent_min = 0,
 | 
			
		||||
        .percent_max = 30,
 | 
			
		||||
        .descr = "Stack remove: cmd error",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageRadioErase,
 | 
			
		||||
        .percent_min = 31,
 | 
			
		||||
        .percent_max = 100,
 | 
			
		||||
        .descr = "Stack remove: wait failed",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageRadioWrite,
 | 
			
		||||
        .percent_min = 0,
 | 
			
		||||
        .percent_max = 100,
 | 
			
		||||
        .descr = "Stack write: error",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageRadioInstall,
 | 
			
		||||
        .percent_min = 0,
 | 
			
		||||
        .percent_max = 10,
 | 
			
		||||
        .descr = "Stack install: cmd error",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageRadioInstall,
 | 
			
		||||
        .percent_min = 11,
 | 
			
		||||
        .percent_max = 100,
 | 
			
		||||
        .descr = "Stack install: wait failed",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageRadioBusy,
 | 
			
		||||
        .percent_min = 0,
 | 
			
		||||
        .percent_max = 10,
 | 
			
		||||
        .descr = "Failed to start C2",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageRadioBusy,
 | 
			
		||||
        .percent_min = 11,
 | 
			
		||||
        .percent_max = 20,
 | 
			
		||||
        .descr = "C2 FUS swich failed",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageRadioBusy,
 | 
			
		||||
        .percent_min = 21,
 | 
			
		||||
        .percent_max = 30,
 | 
			
		||||
        .descr = "FUS operation failed",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageRadioBusy,
 | 
			
		||||
        .percent_min = 31,
 | 
			
		||||
        .percent_max = 100,
 | 
			
		||||
        .descr = "C2 Stach switch failed",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageOBValidation,
 | 
			
		||||
        .percent_min = 0,
 | 
			
		||||
        .percent_max = 100,
 | 
			
		||||
        .descr = "Uncorr. value mismatch",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageValidateDFUImage,
 | 
			
		||||
        .percent_min = 0,
 | 
			
		||||
        .percent_max = 1,
 | 
			
		||||
        .descr = "Failed to open DFU file",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageValidateDFUImage,
 | 
			
		||||
        .percent_min = 1,
 | 
			
		||||
        .percent_max = 97,
 | 
			
		||||
        .descr = "DFU file read error",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageValidateDFUImage,
 | 
			
		||||
        .percent_min = 98,
 | 
			
		||||
        .percent_max = 100,
 | 
			
		||||
        .descr = "DFU file CRC mismatch",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageFlashWrite,
 | 
			
		||||
        .percent_min = 0,
 | 
			
		||||
        .percent_max = 100,
 | 
			
		||||
        .descr = "Flash write error",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageFlashValidate,
 | 
			
		||||
        .percent_min = 0,
 | 
			
		||||
        .percent_max = 100,
 | 
			
		||||
        .descr = "Flash compare error",
 | 
			
		||||
    },
 | 
			
		||||
#endif
 | 
			
		||||
#ifndef FURI_RAM_EXEC
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageLfsRestore,
 | 
			
		||||
        .percent_min = 0,
 | 
			
		||||
        .percent_max = 100,
 | 
			
		||||
        .descr = "LFS I/O error",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        .stage = UpdateTaskStageResourcesUpdate,
 | 
			
		||||
        .percent_min = 0,
 | 
			
		||||
        .percent_max = 100,
 | 
			
		||||
        .descr = "SD card I/O error",
 | 
			
		||||
    },
 | 
			
		||||
#endif
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static const char* update_task_get_error_message(UpdateTaskStage stage, uint8_t percent) {
 | 
			
		||||
    for(size_t i = 0; i < COUNT_OF(update_task_error_detail); i++) {
 | 
			
		||||
        if(update_task_error_detail[i].stage == stage &&
 | 
			
		||||
           percent >= update_task_error_detail[i].percent_min &&
 | 
			
		||||
           percent <= update_task_error_detail[i].percent_max) {
 | 
			
		||||
            return update_task_error_detail[i].descr;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return "Unknown error";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
typedef struct {
 | 
			
		||||
    UpdateTaskStageGroup group;
 | 
			
		||||
    uint8_t weight;
 | 
			
		||||
@ -111,8 +296,9 @@ void update_task_set_progress(UpdateTask* update_task, UpdateTaskStage stage, ui
 | 
			
		||||
        if(stage >= UpdateTaskStageError) {
 | 
			
		||||
            furi_string_printf(
 | 
			
		||||
                update_task->state.status,
 | 
			
		||||
                "%s #[%d-%d]",
 | 
			
		||||
                update_task_stage_descr[stage],
 | 
			
		||||
                "%s\n#[%d-%d]",
 | 
			
		||||
                update_task_get_error_message(
 | 
			
		||||
                    update_task->state.stage, update_task->state.stage_progress),
 | 
			
		||||
                update_task->state.stage,
 | 
			
		||||
                update_task->state.stage_progress);
 | 
			
		||||
        } else {
 | 
			
		||||
 | 
			
		||||
@ -24,3 +24,8 @@ bool update_task_open_file(UpdateTask* update_task, FuriString* filename);
 | 
			
		||||
 | 
			
		||||
int32_t update_task_worker_flash_writer(void* context);
 | 
			
		||||
int32_t update_task_worker_backup_restore(void* context);
 | 
			
		||||
 | 
			
		||||
#define CHECK_RESULT(x) \
 | 
			
		||||
    if(!(x)) {          \
 | 
			
		||||
        break;          \
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -15,11 +15,6 @@
 | 
			
		||||
 | 
			
		||||
#define TAG "UpdWorkerBackup"
 | 
			
		||||
 | 
			
		||||
#define CHECK_RESULT(x) \
 | 
			
		||||
    if(!(x)) {          \
 | 
			
		||||
        break;          \
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
static bool update_task_pre_update(UpdateTask* update_task) {
 | 
			
		||||
    bool success = false;
 | 
			
		||||
    FuriString* backup_file_path;
 | 
			
		||||
 | 
			
		||||
@ -13,11 +13,6 @@
 | 
			
		||||
 | 
			
		||||
#define TAG "UpdWorkerRAM"
 | 
			
		||||
 | 
			
		||||
#define CHECK_RESULT(x) \
 | 
			
		||||
    if(!(x)) {          \
 | 
			
		||||
        break;          \
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
#define STM_DFU_VENDOR_ID 0x0483
 | 
			
		||||
#define STM_DFU_PRODUCT_ID 0xDF11
 | 
			
		||||
/* Written into DFU file by build pipeline */
 | 
			
		||||
@ -137,7 +132,7 @@ static bool update_task_write_stack_data(UpdateTask* update_task) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void update_task_wait_for_restart(UpdateTask* update_task) {
 | 
			
		||||
    update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 10);
 | 
			
		||||
    update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 70);
 | 
			
		||||
    furi_delay_ms(C2_MODE_SWITCH_TIMEOUT);
 | 
			
		||||
    furi_crash("C2 timeout");
 | 
			
		||||
}
 | 
			
		||||
@ -153,12 +148,12 @@ static bool update_task_write_stack(UpdateTask* update_task) {
 | 
			
		||||
            manifest->radio_crc);
 | 
			
		||||
 | 
			
		||||
        CHECK_RESULT(update_task_write_stack_data(update_task));
 | 
			
		||||
        update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 0);
 | 
			
		||||
        update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 10);
 | 
			
		||||
        CHECK_RESULT(
 | 
			
		||||
            ble_glue_fus_stack_install(manifest->radio_address, 0) != BleGlueCommandResultError);
 | 
			
		||||
        update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 80);
 | 
			
		||||
        update_task_set_progress(update_task, UpdateTaskStageProgress, 80);
 | 
			
		||||
        CHECK_RESULT(ble_glue_fus_wait_operation() == BleGlueCommandResultOK);
 | 
			
		||||
        update_task_set_progress(update_task, UpdateTaskStageRadioInstall, 100);
 | 
			
		||||
        update_task_set_progress(update_task, UpdateTaskStageProgress, 100);
 | 
			
		||||
        /* ...system will restart here. */
 | 
			
		||||
        update_task_wait_for_restart(update_task);
 | 
			
		||||
    } while(false);
 | 
			
		||||
@ -170,9 +165,9 @@ static bool update_task_remove_stack(UpdateTask* update_task) {
 | 
			
		||||
        FURI_LOG_W(TAG, "Removing stack");
 | 
			
		||||
        update_task_set_progress(update_task, UpdateTaskStageRadioErase, 30);
 | 
			
		||||
        CHECK_RESULT(ble_glue_fus_stack_delete() != BleGlueCommandResultError);
 | 
			
		||||
        update_task_set_progress(update_task, UpdateTaskStageRadioErase, 80);
 | 
			
		||||
        update_task_set_progress(update_task, UpdateTaskStageProgress, 80);
 | 
			
		||||
        CHECK_RESULT(ble_glue_fus_wait_operation() == BleGlueCommandResultOK);
 | 
			
		||||
        update_task_set_progress(update_task, UpdateTaskStageRadioErase, 100);
 | 
			
		||||
        update_task_set_progress(update_task, UpdateTaskStageProgress, 100);
 | 
			
		||||
        /* ...system will restart here. */
 | 
			
		||||
        update_task_wait_for_restart(update_task);
 | 
			
		||||
    } while(false);
 | 
			
		||||
@ -180,6 +175,7 @@ static bool update_task_remove_stack(UpdateTask* update_task) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool update_task_manage_radiostack(UpdateTask* update_task) {
 | 
			
		||||
    update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 10);
 | 
			
		||||
    bool success = false;
 | 
			
		||||
    do {
 | 
			
		||||
        CHECK_RESULT(ble_glue_wait_for_c2_start(FURI_HAL_BT_C2_START_TIMEOUT));
 | 
			
		||||
@ -208,15 +204,17 @@ static bool update_task_manage_radiostack(UpdateTask* update_task) {
 | 
			
		||||
                /* Version or type mismatch. Let's boot to FUS and start updating. */
 | 
			
		||||
                FURI_LOG_W(TAG, "Restarting to FUS");
 | 
			
		||||
                furi_hal_rtc_set_flag(FuriHalRtcFlagC2Update);
 | 
			
		||||
                update_task_set_progress(update_task, UpdateTaskStageProgress, 20);
 | 
			
		||||
 | 
			
		||||
                CHECK_RESULT(furi_hal_bt_ensure_c2_mode(BleGlueC2ModeFUS));
 | 
			
		||||
                /* ...system will restart here. */
 | 
			
		||||
                update_task_wait_for_restart(update_task);
 | 
			
		||||
            }
 | 
			
		||||
        } else if(c2_state->mode == BleGlueC2ModeFUS) {
 | 
			
		||||
            /* OK, we're in FUS mode. */
 | 
			
		||||
            update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 10);
 | 
			
		||||
            FURI_LOG_W(TAG, "Waiting for FUS to settle");
 | 
			
		||||
            ble_glue_fus_wait_operation();
 | 
			
		||||
            update_task_set_progress(update_task, UpdateTaskStageProgress, 30);
 | 
			
		||||
            CHECK_RESULT(ble_glue_fus_wait_operation() == BleGlueCommandResultOK);
 | 
			
		||||
            if(stack_version_match) {
 | 
			
		||||
                /* We can't check StackType with FUS, but partial version matches */
 | 
			
		||||
                if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagC2Update)) {
 | 
			
		||||
@ -230,7 +228,7 @@ static bool update_task_manage_radiostack(UpdateTask* update_task) {
 | 
			
		||||
                    /* We might just had the stack installed.
 | 
			
		||||
                     * Let's start it up to check its version */
 | 
			
		||||
                    FURI_LOG_W(TAG, "Starting stack to check full version");
 | 
			
		||||
                    update_task_set_progress(update_task, UpdateTaskStageRadioBusy, 40);
 | 
			
		||||
                    update_task_set_progress(update_task, UpdateTaskStageProgress, 50);
 | 
			
		||||
                    CHECK_RESULT(furi_hal_bt_ensure_c2_mode(BleGlueC2ModeStack));
 | 
			
		||||
                    /* ...system will restart here. */
 | 
			
		||||
                    update_task_wait_for_restart(update_task);
 | 
			
		||||
 | 
			
		||||
@ -81,16 +81,17 @@ static void updater_main_draw_callback(Canvas* canvas, void* _model) {
 | 
			
		||||
    canvas_set_font(canvas, FontPrimary);
 | 
			
		||||
 | 
			
		||||
    if(model->failed) {
 | 
			
		||||
        canvas_draw_str_aligned(canvas, 42, 16, AlignLeft, AlignTop, "Update Failed!");
 | 
			
		||||
        canvas_draw_icon(canvas, 2, 22, &I_Warning_30x23);
 | 
			
		||||
        canvas_draw_str_aligned(canvas, 40, 9, AlignLeft, AlignTop, "Update Failed!");
 | 
			
		||||
        canvas_set_font(canvas, FontSecondary);
 | 
			
		||||
        canvas_draw_str_aligned(
 | 
			
		||||
            canvas, 42, 32, AlignLeft, AlignTop, furi_string_get_cstr(model->status));
 | 
			
		||||
 | 
			
		||||
        canvas_draw_icon(canvas, 7, 16, &I_Warning_30x23);
 | 
			
		||||
        elements_multiline_text_aligned(
 | 
			
		||||
            canvas, 75, 26, AlignCenter, AlignTop, furi_string_get_cstr(model->status));
 | 
			
		||||
 | 
			
		||||
        canvas_draw_str_aligned(
 | 
			
		||||
            canvas, 18, 51, AlignLeft, AlignTop, "to retry, hold       to abort");
 | 
			
		||||
        canvas_draw_icon(canvas, 7, 50, &I_Ok_btn_9x9);
 | 
			
		||||
        canvas_draw_icon(canvas, 75, 51, &I_Pin_back_arrow_10x8);
 | 
			
		||||
            canvas, 18, 55, AlignLeft, AlignTop, "to retry, hold       to abort");
 | 
			
		||||
        canvas_draw_icon(canvas, 7, 54, &I_Ok_btn_9x9);
 | 
			
		||||
        canvas_draw_icon(canvas, 75, 55, &I_Pin_back_arrow_10x8);
 | 
			
		||||
    } else {
 | 
			
		||||
        canvas_draw_str_aligned(canvas, 55, 14, AlignLeft, AlignTop, "UPDATING");
 | 
			
		||||
        canvas_set_font(canvas, FontSecondary);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_My_dude_128x64/frame_0.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_My_dude_128x64/frame_1.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_My_dude_128x64/frame_10.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.0 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_My_dude_128x64/frame_11.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 990 B  | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_My_dude_128x64/frame_12.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.1 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_My_dude_128x64/frame_13.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.5 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_My_dude_128x64/frame_14.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_My_dude_128x64/frame_15.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_My_dude_128x64/frame_16.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_My_dude_128x64/frame_17.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_My_dude_128x64/frame_18.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_My_dude_128x64/frame_19.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.6 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								assets/dolphin/external/L1_My_dude_128x64/frame_2.png
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.6 KiB  |