mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-06-16 20:09:44 -07:00
Merge branch 'dev' into skorp/subghz_nice_flo_20bit
This commit is contained in:
+7
-2
@@ -18,7 +18,7 @@
|
||||
/applications/main/gpio/ @skotopes @DrZlo13 @hedger @nminaylov
|
||||
/applications/main/ibutton/ @skotopes @DrZlo13 @hedger @gsurkov
|
||||
/applications/main/infrared/ @skotopes @DrZlo13 @hedger @gsurkov
|
||||
/applications/main/nfc/ @skotopes @DrZlo13 @hedger @gornekich
|
||||
/applications/main/nfc/ @skotopes @DrZlo13 @hedger @gornekich @Astrrra
|
||||
/applications/main/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm
|
||||
/applications/main/u2f/ @skotopes @DrZlo13 @hedger @nminaylov
|
||||
|
||||
@@ -40,6 +40,8 @@
|
||||
|
||||
/applications/system/storage_move_to_sd/ @skotopes @DrZlo13 @hedger @nminaylov
|
||||
|
||||
/applications/debug/unit_tests/ @skotopes @DrZlo13 @hedger @nminaylov @gornekich @Astrrra @gsurkov @Skorpionm
|
||||
|
||||
# Documentation
|
||||
/documentation/ @skotopes @DrZlo13 @hedger @drunkbatya
|
||||
/scripts/toolchain/ @skotopes @DrZlo13 @hedger @drunkbatya
|
||||
@@ -54,6 +56,9 @@
|
||||
/lib/mbedtls/ @skotopes @DrZlo13 @hedger @nminaylov
|
||||
/lib/micro-ecc/ @skotopes @DrZlo13 @hedger @nminaylov
|
||||
/lib/nanopb/ @skotopes @DrZlo13 @hedger @nminaylov
|
||||
/lib/nfc/ @skotopes @DrZlo13 @hedger @gornekich
|
||||
/lib/nfc/ @skotopes @DrZlo13 @hedger @gornekich @Astrrra
|
||||
/lib/one_wire/ @skotopes @DrZlo13 @hedger @gsurkov
|
||||
/lib/subghz/ @skotopes @DrZlo13 @hedger @Skorpionm
|
||||
|
||||
# CI/CD
|
||||
/.github/workflows/ @skotopes @DrZlo13 @hedger @drunkbatya
|
||||
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
export RODATA_SIZE="$(get_size ".rodata")"
|
||||
export DATA_SIZE="$(get_size ".data")"
|
||||
export FREE_FLASH_SIZE="$(get_size ".free_flash")"
|
||||
python3 -m pip install mariadb
|
||||
python3 -m pip install mariadb==1.1.4
|
||||
python3 scripts/amap_mariadb_insert.py \
|
||||
${{ secrets.AMAP_MARIADB_USER }} \
|
||||
${{ secrets.AMAP_MARIADB_PASSWORD }} \
|
||||
|
||||
+3
-6
@@ -7,6 +7,7 @@
|
||||
# construction of certain targets behind command-line options.
|
||||
|
||||
import os
|
||||
from fbt.util import path_as_posix
|
||||
|
||||
DefaultEnvironment(tools=[])
|
||||
|
||||
@@ -200,9 +201,7 @@ firmware_debug = distenv.PhonyTarget(
|
||||
source=firmware_env["FW_ELF"],
|
||||
GDBOPTS="${GDBOPTS_BASE}",
|
||||
GDBREMOTE="${OPENOCD_GDB_PIPE}",
|
||||
FBT_FAP_DEBUG_ELF_ROOT=firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT").replace(
|
||||
"\\", "/"
|
||||
),
|
||||
FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")),
|
||||
)
|
||||
distenv.Depends(firmware_debug, firmware_flash)
|
||||
|
||||
@@ -212,9 +211,7 @@ distenv.PhonyTarget(
|
||||
source=firmware_env["FW_ELF"],
|
||||
GDBOPTS="${GDBOPTS_BASE} ${GDBOPTS_BLACKMAGIC}",
|
||||
GDBREMOTE="${BLACKMAGIC_ADDR}",
|
||||
FBT_FAP_DEBUG_ELF_ROOT=firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT").replace(
|
||||
"\\", "/"
|
||||
),
|
||||
FBT_FAP_DEBUG_ELF_ROOT=path_as_posix(firmware_env.subst("$FBT_FAP_DEBUG_ELF_ROOT")),
|
||||
)
|
||||
|
||||
# Debug alien elf
|
||||
|
||||
@@ -0,0 +1,247 @@
|
||||
#include "acurite_609txc.h"
|
||||
|
||||
#define TAG "WSProtocolAcurite_609TXC"
|
||||
|
||||
/*
|
||||
* Help
|
||||
* https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c#L216
|
||||
*
|
||||
* 0000 1111 | 0011 0000 | 0101 1100 | 0000 0000 | 1110 0111
|
||||
* iiii iiii | buuu tttt | tttt tttt | hhhh hhhh | cccc cccc
|
||||
* - i: identification; changes on battery switch
|
||||
* - c: checksum (sum of previous by bytes)
|
||||
* - u: unknown
|
||||
* - b: battery low; flag to indicate low battery voltage
|
||||
* - t: temperature; in °C * 10, 12 bit with complement
|
||||
* - h: humidity
|
||||
*
|
||||
*/
|
||||
|
||||
static const SubGhzBlockConst ws_protocol_acurite_609txc_const = {
|
||||
.te_short = 500,
|
||||
.te_long = 1000,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 40,
|
||||
};
|
||||
|
||||
struct WSProtocolDecoderAcurite_609TXC {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
WSBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct WSProtocolEncoderAcurite_609TXC {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
WSBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
Acurite_609TXCDecoderStepReset = 0,
|
||||
Acurite_609TXCDecoderStepSaveDuration,
|
||||
Acurite_609TXCDecoderStepCheckDuration,
|
||||
} Acurite_609TXCDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder ws_protocol_acurite_609txc_decoder = {
|
||||
.alloc = ws_protocol_decoder_acurite_609txc_alloc,
|
||||
.free = ws_protocol_decoder_acurite_609txc_free,
|
||||
|
||||
.feed = ws_protocol_decoder_acurite_609txc_feed,
|
||||
.reset = ws_protocol_decoder_acurite_609txc_reset,
|
||||
|
||||
.get_hash_data = ws_protocol_decoder_acurite_609txc_get_hash_data,
|
||||
.serialize = ws_protocol_decoder_acurite_609txc_serialize,
|
||||
.deserialize = ws_protocol_decoder_acurite_609txc_deserialize,
|
||||
.get_string = ws_protocol_decoder_acurite_609txc_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder ws_protocol_acurite_609txc_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol ws_protocol_acurite_609txc = {
|
||||
.name = WS_PROTOCOL_ACURITE_609TXC_NAME,
|
||||
.type = SubGhzProtocolWeatherStation,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
|
||||
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||
|
||||
.decoder = &ws_protocol_acurite_609txc_decoder,
|
||||
.encoder = &ws_protocol_acurite_609txc_encoder,
|
||||
};
|
||||
|
||||
void* ws_protocol_decoder_acurite_609txc_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
WSProtocolDecoderAcurite_609TXC* instance = malloc(sizeof(WSProtocolDecoderAcurite_609TXC));
|
||||
instance->base.protocol = &ws_protocol_acurite_609txc;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_acurite_609txc_free(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderAcurite_609TXC* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_acurite_609txc_reset(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderAcurite_609TXC* instance = context;
|
||||
instance->decoder.parser_step = Acurite_609TXCDecoderStepReset;
|
||||
}
|
||||
|
||||
static bool ws_protocol_acurite_609txc_check(WSProtocolDecoderAcurite_609TXC* instance) {
|
||||
if(!instance->decoder.decode_data) return false;
|
||||
uint8_t crc = (uint8_t)(instance->decoder.decode_data >> 32) +
|
||||
(uint8_t)(instance->decoder.decode_data >> 24) +
|
||||
(uint8_t)(instance->decoder.decode_data >> 16) +
|
||||
(uint8_t)(instance->decoder.decode_data >> 8);
|
||||
return (crc == (instance->decoder.decode_data & 0xFF));
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a WSBlockGeneric* instance
|
||||
*/
|
||||
static void ws_protocol_acurite_609txc_remote_controller(WSBlockGeneric* instance) {
|
||||
instance->id = (instance->data >> 32) & 0xFF;
|
||||
instance->battery_low = (instance->data >> 31) & 1;
|
||||
|
||||
instance->channel = WS_NO_CHANNEL;
|
||||
|
||||
// Temperature in Celsius is encoded as a 12 bit integer value
|
||||
// multiplied by 10 using the 4th - 6th nybbles (bytes 1 & 2)
|
||||
// negative values are recovered by sign extend from int16_t.
|
||||
int16_t temp_raw =
|
||||
(int16_t)(((instance->data >> 12) & 0xf000) | ((instance->data >> 16) << 4));
|
||||
instance->temp = (temp_raw >> 4) * 0.1f;
|
||||
instance->humidity = (instance->data >> 8) & 0xff;
|
||||
instance->btn = WS_NO_BTN;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_acurite_609txc_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderAcurite_609TXC* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case Acurite_609TXCDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_short * 17) <
|
||||
ws_protocol_acurite_609txc_const.te_delta * 8)) {
|
||||
//Found syncPrefix
|
||||
instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_609TXCDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = Acurite_609TXCDecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = Acurite_609TXCDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case Acurite_609TXCDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
if(DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_609txc_const.te_short) <
|
||||
ws_protocol_acurite_609txc_const.te_delta) {
|
||||
if((DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_short) <
|
||||
ws_protocol_acurite_609txc_const.te_delta) ||
|
||||
(duration > ws_protocol_acurite_609txc_const.te_long * 3)) {
|
||||
//Found syncPostfix
|
||||
instance->decoder.parser_step = Acurite_609TXCDecoderStepReset;
|
||||
if((instance->decoder.decode_count_bit ==
|
||||
ws_protocol_acurite_609txc_const.min_count_bit_for_found) &&
|
||||
ws_protocol_acurite_609txc_check(instance)) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
ws_protocol_acurite_609txc_remote_controller(&instance->generic);
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
} else if(
|
||||
DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_long) <
|
||||
ws_protocol_acurite_609txc_const.te_delta * 2) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration;
|
||||
} else if(
|
||||
DURATION_DIFF(duration, ws_protocol_acurite_609txc_const.te_long * 2) <
|
||||
ws_protocol_acurite_609txc_const.te_delta * 4) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = Acurite_609TXCDecoderStepSaveDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = Acurite_609TXCDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = Acurite_609TXCDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = Acurite_609TXCDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ws_protocol_decoder_acurite_609txc_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderAcurite_609TXC* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
bool ws_protocol_decoder_acurite_609txc_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderAcurite_609TXC* instance = context;
|
||||
return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
bool ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderAcurite_609TXC* instance = context;
|
||||
bool ret = false;
|
||||
do {
|
||||
if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
|
||||
break;
|
||||
}
|
||||
if(instance->generic.data_count_bit !=
|
||||
ws_protocol_acurite_609txc_const.min_count_bit_for_found) {
|
||||
FURI_LOG_E(TAG, "Wrong number of bits in key");
|
||||
break;
|
||||
}
|
||||
ret = true;
|
||||
} while(false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_acurite_609txc_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderAcurite_609TXC* instance = context;
|
||||
furi_string_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:0x%lX%08lX\r\n"
|
||||
"Sn:0x%lX Ch:%d Bat:%d\r\n"
|
||||
"Temp:%3.1f C Hum:%d%%",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 40),
|
||||
(uint32_t)(instance->generic.data),
|
||||
instance->generic.id,
|
||||
instance->generic.channel,
|
||||
instance->generic.battery_low,
|
||||
(double)instance->generic.temp,
|
||||
instance->generic.humidity);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include "ws_generic.h"
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
|
||||
#define WS_PROTOCOL_ACURITE_609TXC_NAME "Acurite-609TXC"
|
||||
|
||||
typedef struct WSProtocolDecoderAcurite_609TXC WSProtocolDecoderAcurite_609TXC;
|
||||
typedef struct WSProtocolEncoderAcurite_609TXC WSProtocolEncoderAcurite_609TXC;
|
||||
|
||||
extern const SubGhzProtocolDecoder ws_protocol_acurite_609txc_decoder;
|
||||
extern const SubGhzProtocolEncoder ws_protocol_acurite_609txc_encoder;
|
||||
extern const SubGhzProtocol ws_protocol_acurite_609txc;
|
||||
|
||||
/**
|
||||
* Allocate WSProtocolDecoderAcurite_609TXC.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return WSProtocolDecoderAcurite_609TXC* pointer to a WSProtocolDecoderAcurite_609TXC instance
|
||||
*/
|
||||
void* ws_protocol_decoder_acurite_609txc_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free WSProtocolDecoderAcurite_609TXC.
|
||||
* @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance
|
||||
*/
|
||||
void ws_protocol_decoder_acurite_609txc_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder WSProtocolDecoderAcurite_609TXC.
|
||||
* @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance
|
||||
*/
|
||||
void ws_protocol_decoder_acurite_609txc_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void ws_protocol_decoder_acurite_609txc_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t ws_protocol_decoder_acurite_609txc_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data WSProtocolDecoderAcurite_609TXC.
|
||||
* @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return true On success
|
||||
*/
|
||||
bool ws_protocol_decoder_acurite_609txc_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data WSProtocolDecoderAcurite_609TXC.
|
||||
* @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return true On success
|
||||
*/
|
||||
bool ws_protocol_decoder_acurite_609txc_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a WSProtocolDecoderAcurite_609TXC instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void ws_protocol_decoder_acurite_609txc_get_string(void* context, FuriString* output);
|
||||
@@ -6,6 +6,7 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = {
|
||||
&ws_protocol_nexus_th,
|
||||
&ws_protocol_gt_wt_03,
|
||||
&ws_protocol_acurite_606tx,
|
||||
&ws_protocol_acurite_609txc,
|
||||
&ws_protocol_lacrosse_tx141thbv2,
|
||||
&ws_protocol_oregon2,
|
||||
&ws_protocol_acurite_592txr,
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "nexus_th.h"
|
||||
#include "gt_wt_03.h"
|
||||
#include "acurite_606tx.h"
|
||||
#include "acurite_609txc.h"
|
||||
#include "lacrosse_tx141thbv2.h"
|
||||
#include "oregon2.h"
|
||||
#include "acurite_592txr.h"
|
||||
|
||||
@@ -273,11 +273,7 @@ static void loader_thread_state_callback(FuriThreadState thread_state, void* con
|
||||
furi_hal_power_insomnia_enter();
|
||||
}
|
||||
} else if(thread_state == FuriThreadStateStopped) {
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Application thread stopped. Free heap: %d. Thread allocation balance: %d.",
|
||||
memmgr_get_free_heap(),
|
||||
furi_thread_get_heap_size(instance->application_thread));
|
||||
FURI_LOG_I(TAG, "Application stopped. Free heap: %d", memmgr_get_free_heap());
|
||||
|
||||
if(loader_instance->application_arguments) {
|
||||
free(loader_instance->application_arguments);
|
||||
|
||||
@@ -372,7 +372,7 @@ RpcSession* rpc_session_open(Rpc* rpc) {
|
||||
|
||||
session->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(session->thread, "RpcSessionWorker");
|
||||
furi_thread_set_stack_size(session->thread, 2048);
|
||||
furi_thread_set_stack_size(session->thread, 3072);
|
||||
furi_thread_set_context(session->thread, session);
|
||||
furi_thread_set_callback(session->thread, rpc_session_worker);
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ Only 2 parameters are mandatory: ***appid*** and ***apptype***, others are optio
|
||||
* **icon**: Animated icon name from built-in assets to be used when building app as a part of firmware.
|
||||
* **order**: Order of an application within its group when sorting entries in it. The lower the order is, the closer to the start of the list the item is placed. *Used for ordering startup hooks and menu entries.*
|
||||
* **sdk_headers**: List of C header files from this app's code to include in API definitions for external applications.
|
||||
* **targets**: list of strings, target names, which this application is compatible with. If not specified, application is built for all targets. Default value is `["all"]`.
|
||||
|
||||
|
||||
#### Parameters for external applications
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# Unit tests
|
||||
## Intro
|
||||
Unit tests are special pieces of code that apply known inputs to the feature code and check the results to see if they were correct.
|
||||
They are crucial for writing robust, bug-free code.
|
||||
|
||||
Flipper Zero firmware includes a separate application called [unit_tests](/applications/debug/unit_tests).
|
||||
It is run directly on the Flipper Zero in order to employ its hardware features and to rule out any platform-related differences.
|
||||
|
||||
When contributing code to the Flipper Zero firmware, it is highly desirable to supply unit tests along with the proposed features.
|
||||
Running existing unit tests is useful to ensure that the new code doesn't introduce any regressions.
|
||||
|
||||
## Running unit tests
|
||||
In order to run the unit tests, follow these steps:
|
||||
1. Compile the firmware with the tests enabled: `./fbt FIRMWARE_APP_SET=unit_tests`.
|
||||
2. Flash the firmware using your preferred method.
|
||||
3. Copy the [assets/unit_tests](assets/unit_tests) folder to the root your Flipper Zero's SD card.
|
||||
4. Launch the CLI session and run the `unit_tests` command.
|
||||
|
||||
**NOTE:** To run a particular test (and skip all others), specify its name as the command argument.
|
||||
See [test_index.c](applications/debug/unit_tests/test_index.c) for the complete list of test names.
|
||||
|
||||
## Adding unit tests
|
||||
### General
|
||||
#### Entry point
|
||||
The common entry point for all tests it the [unit_tests](applications/debug/unit_tests) application. Test-specific code is placed into an arbitrarily named subdirectory and is then called from the [test_index.c](applications/debug/unit_tests/test_index.c) source file.
|
||||
#### Test assets
|
||||
Some unit tests require external data in order to function. These files (commonly called assets) reside in the [assets/unit_tests](/assets/unit_tests) directory in their respective subdirectories. Asset files can be of any type (plain text, FlipperFormat(FFF), binary etc).
|
||||
### Application-specific
|
||||
#### Infrared
|
||||
Each infrared protocol has a corresponding set of unit tests, so it makes sense to implement one when adding support for a new protocol.
|
||||
In order to add unit tests for your protocol, follow these steps:
|
||||
1. Create a file named `test_<your_protocol_name>.irtest` in the [assets](assets/unit_tests/infrared) directory.
|
||||
2. Fill it with the test data (more on it below).
|
||||
3. Add the test code to [infrared_test.c](applications/debug/unit_tests/infrared/infrared_test.c).
|
||||
4. Update the [assets](assets/unit_tests/infrared) on your Flipper Zero and run the tests to see if they pass.
|
||||
|
||||
##### Test data format
|
||||
Each unit test has 3 sections:
|
||||
1. `decoder` - takes in raw signal and outputs decoded messages.
|
||||
2. `encoder` - takes in decoded messages and outputs raw signal.
|
||||
3. `encoder_decoder` - takes in decoded messages, turns them into raw signal and then decodes again.
|
||||
|
||||
Infrared test asset files have an `.irtest` extension and are regular `.ir` files with a few additions.
|
||||
Decoder input data has signal names `decoder_input_N`, where N is a test sequence number. Expected data goes under the name `decoder_expected_N`. When testing the encoder these two are switched.
|
||||
|
||||
Decoded data is represented in arrays (since a single raw signal may decode to several messages). If there is only one signal, then it has to be an array of size 1. Use the existing files as syntax examples.
|
||||
|
||||
##### Getting raw signals
|
||||
Recording raw IR signals is possible using Flipper Zero. Launch the CLI session, run `ir rx raw`, then point the remote towards Flipper's receiver and send the signals. The raw signal data will be printed to the console in a convenient format.
|
||||
+2
-2
@@ -40,11 +40,11 @@ env = ENV.Clone(
|
||||
FW_LIB_OPTS={
|
||||
"Default": {
|
||||
"CCFLAGS": [
|
||||
"-Os",
|
||||
"-Og" if ENV["LIB_DEBUG"] else "-Os",
|
||||
],
|
||||
"CPPDEFINES": [
|
||||
"NDEBUG",
|
||||
"FURI_NDEBUG",
|
||||
"FURI_DEBUG" if ENV["LIB_DEBUG"] else "FURI_NDEBUG",
|
||||
],
|
||||
# You can add other entries named after libraries
|
||||
# If they are present, they have precedence over Default
|
||||
|
||||
+10
-2
@@ -12,6 +12,8 @@
|
||||
#include <furi_hal_rtc.h>
|
||||
#include <furi_hal_console.h>
|
||||
|
||||
#define TAG "FuriThread"
|
||||
|
||||
#define THREAD_NOTIFY_INDEX 1 // Index 0 is used for stream buffers
|
||||
|
||||
typedef struct FuriThreadStdout FuriThreadStdout;
|
||||
@@ -82,6 +84,12 @@ static void furi_thread_body(void* context) {
|
||||
if(thread->heap_trace_enabled == true) {
|
||||
furi_delay_ms(33);
|
||||
thread->heap_size = memmgr_heap_get_thread_memory((FuriThreadId)task_handle);
|
||||
furi_log_print_format(
|
||||
thread->heap_size ? FuriLogLevelError : FuriLogLevelInfo,
|
||||
TAG,
|
||||
"%s allocation balance: %d",
|
||||
thread->name ? thread->name : "Thread",
|
||||
thread->heap_size);
|
||||
memmgr_heap_disable_thread_trace((FuriThreadId)task_handle);
|
||||
}
|
||||
|
||||
@@ -89,8 +97,8 @@ static void furi_thread_body(void* context) {
|
||||
|
||||
if(thread->is_service) {
|
||||
FURI_LOG_E(
|
||||
"Service",
|
||||
"%s thread exited. Thread memory cannot be reclaimed.",
|
||||
TAG,
|
||||
"%s service thread exited. Thread memory cannot be reclaimed.",
|
||||
thread->name ? thread->name : "<unknown service>");
|
||||
}
|
||||
|
||||
|
||||
@@ -70,12 +70,12 @@ void nfc_worker_start(
|
||||
|
||||
void nfc_worker_stop(NfcWorker* nfc_worker) {
|
||||
furi_assert(nfc_worker);
|
||||
if(nfc_worker->state == NfcWorkerStateBroken || nfc_worker->state == NfcWorkerStateReady) {
|
||||
return;
|
||||
furi_assert(nfc_worker->thread);
|
||||
if(furi_thread_get_state(nfc_worker->thread) != FuriThreadStateStopped) {
|
||||
furi_hal_nfc_stop();
|
||||
nfc_worker_change_state(nfc_worker, NfcWorkerStateStop);
|
||||
furi_thread_join(nfc_worker->thread);
|
||||
}
|
||||
furi_hal_nfc_stop();
|
||||
nfc_worker_change_state(nfc_worker, NfcWorkerStateStop);
|
||||
furi_thread_join(nfc_worker->thread);
|
||||
}
|
||||
|
||||
void nfc_worker_change_state(NfcWorker* nfc_worker, NfcWorkerState state) {
|
||||
|
||||
@@ -7,7 +7,6 @@ typedef struct NfcWorker NfcWorker;
|
||||
typedef enum {
|
||||
// Init states
|
||||
NfcWorkerStateNone,
|
||||
NfcWorkerStateBroken,
|
||||
NfcWorkerStateReady,
|
||||
// Main worker states
|
||||
NfcWorkerStateRead,
|
||||
|
||||
@@ -52,6 +52,8 @@ class FlipperApplication:
|
||||
icon: Optional[str] = None
|
||||
order: int = 0
|
||||
sdk_headers: List[str] = field(default_factory=list)
|
||||
targets: List[str] = field(default_factory=lambda: ["all"])
|
||||
|
||||
# .fap-specific
|
||||
sources: List[str] = field(default_factory=lambda: ["*.c*"])
|
||||
fap_version: Tuple[int] = field(default_factory=lambda: (0, 1))
|
||||
@@ -135,8 +137,8 @@ class AppManager:
|
||||
raise FlipperManifestException(f"Duplicate app declaration: {app.appid}")
|
||||
self.known_apps[app.appid] = app
|
||||
|
||||
def filter_apps(self, applist: List[str]):
|
||||
return AppBuildset(self, applist)
|
||||
def filter_apps(self, applist: List[str], hw_target: str):
|
||||
return AppBuildset(self, applist, hw_target)
|
||||
|
||||
|
||||
class AppBuilderException(Exception):
|
||||
@@ -155,11 +157,13 @@ class AppBuildset:
|
||||
FlipperAppType.STARTUP,
|
||||
)
|
||||
|
||||
def __init__(self, appmgr: AppManager, appnames: List[str]):
|
||||
def __init__(self, appmgr: AppManager, appnames: List[str], hw_target: str):
|
||||
self.appmgr = appmgr
|
||||
self.appnames = set(appnames)
|
||||
self.hw_target = hw_target
|
||||
self._orig_appnames = appnames
|
||||
self._process_deps()
|
||||
self._filter_by_target()
|
||||
self._check_conflicts()
|
||||
self._check_unsatisfied() # unneeded?
|
||||
self.apps = sorted(
|
||||
@@ -170,6 +174,16 @@ class AppBuildset:
|
||||
def _is_missing_dep(self, dep_name: str):
|
||||
return dep_name not in self.appnames
|
||||
|
||||
def _filter_by_target(self):
|
||||
for appname in self.appnames.copy():
|
||||
app = self.appmgr.get(appname)
|
||||
# if app.apptype not in self.BUILTIN_APP_TYPES:
|
||||
if not any(map(lambda t: t in app.targets, ["all", self.hw_target])):
|
||||
print(
|
||||
f"Removing {appname} due to target mismatch (building for {self.hw_target}, app supports {app.targets}"
|
||||
)
|
||||
self.appnames.remove(appname)
|
||||
|
||||
def _process_deps(self):
|
||||
while True:
|
||||
provided = []
|
||||
|
||||
+6
-1
@@ -1,7 +1,6 @@
|
||||
import SCons
|
||||
from SCons.Subst import quote_spaces
|
||||
from SCons.Errors import StopError
|
||||
from SCons.Node.FS import _my_normcase
|
||||
|
||||
import re
|
||||
import os
|
||||
@@ -58,3 +57,9 @@ def extract_abs_dir_path(node):
|
||||
raise StopError(f"Can't find absolute path for {node.name}")
|
||||
|
||||
return abs_dir_node.abspath
|
||||
|
||||
|
||||
def path_as_posix(path):
|
||||
if SCons.Platform.platform_default() == "win32":
|
||||
return path.replace(os.path.sep, os.path.altsep)
|
||||
return path
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
from SCons.Builder import Builder
|
||||
from SCons.Action import Action
|
||||
from SCons.Warnings import warn, WarningOnByDefault
|
||||
import SCons
|
||||
from ansi.color import fg
|
||||
|
||||
from fbt.appmanifest import (
|
||||
@@ -33,14 +32,12 @@ def LoadAppManifest(env, entry):
|
||||
|
||||
|
||||
def PrepareApplicationsBuild(env):
|
||||
appbuild = env["APPBUILD"] = env["APPMGR"].filter_apps(env["APPS"])
|
||||
appbuild = env["APPBUILD"] = env["APPMGR"].filter_apps(
|
||||
env["APPS"], env.subst("f${TARGET_HW}")
|
||||
)
|
||||
env.Append(
|
||||
SDK_HEADERS=appbuild.get_sdk_headers(),
|
||||
)
|
||||
env["APPBUILD_DUMP"] = env.Action(
|
||||
DumpApplicationConfig,
|
||||
"\tINFO\t",
|
||||
)
|
||||
|
||||
|
||||
def DumpApplicationConfig(target, source, env):
|
||||
@@ -68,6 +65,10 @@ def generate(env):
|
||||
env.AddMethod(PrepareApplicationsBuild)
|
||||
env.SetDefault(
|
||||
APPMGR=AppManager(),
|
||||
APPBUILD_DUMP=env.Action(
|
||||
DumpApplicationConfig,
|
||||
"\tINFO\t",
|
||||
),
|
||||
)
|
||||
|
||||
env.Append(
|
||||
|
||||
@@ -41,12 +41,12 @@ def generate(env, **kw):
|
||||
"|openocd -c 'gdb_port pipe; log_output ${FBT_DEBUG_DIR}/openocd.log' ${[SINGLEQUOTEFUNC(OPENOCD_OPTS)]}"
|
||||
],
|
||||
GDBOPTS_BASE=[
|
||||
"-ex",
|
||||
"set pagination off",
|
||||
"-ex",
|
||||
"target extended-remote ${GDBREMOTE}",
|
||||
"-ex",
|
||||
"set confirm off",
|
||||
"-ex",
|
||||
"set pagination off",
|
||||
],
|
||||
GDBOPTS_BLACKMAGIC=[
|
||||
"-ex",
|
||||
|
||||
@@ -14,6 +14,7 @@ import json
|
||||
|
||||
from fbt.sdk.collector import SdkCollector
|
||||
from fbt.sdk.cache import SdkCache
|
||||
from fbt.util import path_as_posix
|
||||
|
||||
|
||||
def ProcessSdkDepends(env, filename):
|
||||
@@ -52,6 +53,8 @@ def prebuild_sdk_create_origin_file(target, source, env):
|
||||
|
||||
|
||||
class SdkMeta:
|
||||
MAP_FILE_SUBST = "SDK_MAP_FILE_SUBST"
|
||||
|
||||
def __init__(self, env, tree_builder: "SdkTreeBuilder"):
|
||||
self.env = env
|
||||
self.treebuilder = tree_builder
|
||||
@@ -67,6 +70,7 @@ class SdkMeta:
|
||||
"linker_libs": self.env.subst("${LIBS}"),
|
||||
"app_ep_subst": self.env.subst("${APP_ENTRY}"),
|
||||
"sdk_path_subst": self.env.subst("${SDK_DIR_SUBST}"),
|
||||
"map_file_subst": self.MAP_FILE_SUBST,
|
||||
"hardware": self.env.subst("${TARGET_HW}"),
|
||||
}
|
||||
with open(json_manifest_path, "wt") as f:
|
||||
@@ -75,9 +79,9 @@ class SdkMeta:
|
||||
def _wrap_scons_vars(self, vars: str):
|
||||
expanded_vars = self.env.subst(
|
||||
vars,
|
||||
target=Entry("dummy"),
|
||||
target=Entry(self.MAP_FILE_SUBST),
|
||||
)
|
||||
return expanded_vars.replace("\\", "/")
|
||||
return path_as_posix(expanded_vars)
|
||||
|
||||
|
||||
class SdkTreeBuilder:
|
||||
@@ -142,13 +146,15 @@ class SdkTreeBuilder:
|
||||
meta.save_to(self.target[0].path)
|
||||
|
||||
def build_sdk_file_path(self, orig_path: str) -> str:
|
||||
return posixpath.normpath(
|
||||
posixpath.join(
|
||||
self.SDK_DIR_SUBST,
|
||||
self.target_sdk_dir_name,
|
||||
orig_path,
|
||||
return path_as_posix(
|
||||
posixpath.normpath(
|
||||
posixpath.join(
|
||||
self.SDK_DIR_SUBST,
|
||||
self.target_sdk_dir_name,
|
||||
orig_path,
|
||||
)
|
||||
)
|
||||
).replace("\\", "/")
|
||||
)
|
||||
|
||||
def emitter(self, target, source, env):
|
||||
target_folder = target[0]
|
||||
|
||||
@@ -13,7 +13,7 @@ if not [%FBT_NOENV%] == [] (
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
set "FLIPPER_TOOLCHAIN_VERSION=17"
|
||||
set "FLIPPER_TOOLCHAIN_VERSION=19"
|
||||
|
||||
if [%FBT_TOOLCHAIN_ROOT%] == [] (
|
||||
set "FBT_TOOLCHAIN_ROOT=%FBT_ROOT%\toolchain\x86_64-windows"
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# public variables
|
||||
DEFAULT_SCRIPT_PATH="$(pwd -P)";
|
||||
SCRIPT_PATH="${SCRIPT_PATH:-$DEFAULT_SCRIPT_PATH}";
|
||||
FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"17"}";
|
||||
FBT_TOOLCHAIN_VERSION="${FBT_TOOLCHAIN_VERSION:-"19"}";
|
||||
FBT_TOOLCHAIN_PATH="${FBT_TOOLCHAIN_PATH:-$SCRIPT_PATH}";
|
||||
|
||||
fbtenv_show_usage()
|
||||
|
||||
@@ -63,6 +63,11 @@ vars.AddVariables(
|
||||
help="Enable debug build",
|
||||
default=True,
|
||||
),
|
||||
BoolVariable(
|
||||
"LIB_DEBUG",
|
||||
help="Enable debug build for libraries",
|
||||
default=False,
|
||||
),
|
||||
BoolVariable(
|
||||
"COMPACT",
|
||||
help="Optimize for size",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from dataclasses import dataclass, field
|
||||
from SCons.Errors import UserError
|
||||
from SCons.Node import NodeList
|
||||
from SCons.Warnings import warn, WarningOnByDefault
|
||||
|
||||
|
||||
Import("ENV")
|
||||
@@ -80,6 +80,14 @@ if extra_app_list := GetOption("extra_ext_apps"):
|
||||
known_extapps.extend(map(appenv["APPMGR"].get, extra_app_list.split(",")))
|
||||
|
||||
for app in known_extapps:
|
||||
if not any(map(lambda t: t in app.targets, ["all", appenv.subst("f${TARGET_HW}")])):
|
||||
warn(
|
||||
WarningOnByDefault,
|
||||
f"Can't build '{app.name}' (id '{app.appid}'): target mismatch"
|
||||
f" (building for {appenv.subst('f${TARGET_HW}')}, app supports {app.targets}",
|
||||
)
|
||||
continue
|
||||
|
||||
appenv.BuildAppElf(app)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user