Merge branch 'fz-dev' into dev
@@ -52,7 +52,7 @@ Almost everything in flipper firmware is built around this concept.
|
||||
|
||||
## Naming
|
||||
|
||||
### Type names are CamelCase
|
||||
### Type names are PascalCase
|
||||
|
||||
Examples:
|
||||
|
||||
|
||||
@@ -11,4 +11,5 @@ App(
|
||||
stack_size=1 * 1024,
|
||||
order=130,
|
||||
fap_category="Debug",
|
||||
fap_libs=["assets"],
|
||||
)
|
||||
|
||||
148
applications/debug/battery_test_app/views/battery_info.c
Normal file
@@ -0,0 +1,148 @@
|
||||
#include "battery_info.h"
|
||||
#include <furi.h>
|
||||
#include <gui/elements.h>
|
||||
#include <assets_icons.h>
|
||||
|
||||
#define LOW_CHARGE_THRESHOLD 10
|
||||
#define HIGH_DRAIN_CURRENT_THRESHOLD 100
|
||||
|
||||
struct BatteryInfo {
|
||||
View* view;
|
||||
};
|
||||
|
||||
static void draw_stat(Canvas* canvas, int x, int y, const Icon* icon, char* val) {
|
||||
canvas_draw_frame(canvas, x - 7, y + 7, 30, 13);
|
||||
canvas_draw_icon(canvas, x, y, icon);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, x - 4, y + 16, 24, 6);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_str_aligned(canvas, x + 8, y + 22, AlignCenter, AlignBottom, val);
|
||||
};
|
||||
|
||||
static void draw_battery(Canvas* canvas, BatteryInfoModel* data, int x, int y) {
|
||||
char emote[20] = {};
|
||||
char header[20] = {};
|
||||
char value[20] = {};
|
||||
|
||||
int32_t drain_current = data->gauge_current * (-1000);
|
||||
uint32_t charge_current = data->gauge_current * 1000;
|
||||
|
||||
// Draw battery
|
||||
canvas_draw_icon(canvas, x, y, &I_BatteryBody_52x28);
|
||||
if(charge_current > 0) {
|
||||
canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceCharging_29x14);
|
||||
} else if(drain_current > HIGH_DRAIN_CURRENT_THRESHOLD) {
|
||||
canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceConfused_29x14);
|
||||
} else if(data->charge < LOW_CHARGE_THRESHOLD) {
|
||||
canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceNopower_29x14);
|
||||
} else {
|
||||
canvas_draw_icon(canvas, x + 16, y + 7, &I_FaceNormal_29x14);
|
||||
}
|
||||
|
||||
// Draw bubble
|
||||
elements_bubble(canvas, 53, 0, 71, 39);
|
||||
|
||||
// Set text
|
||||
if(charge_current > 0) {
|
||||
snprintf(emote, sizeof(emote), "%s", "Yummy!");
|
||||
snprintf(header, sizeof(header), "%s", "Charging at");
|
||||
snprintf(
|
||||
value,
|
||||
sizeof(value),
|
||||
"%lu.%luV %lumA",
|
||||
(uint32_t)(data->vbus_voltage),
|
||||
(uint32_t)(data->vbus_voltage * 10) % 10,
|
||||
charge_current);
|
||||
} else if(drain_current > 0) {
|
||||
snprintf(
|
||||
emote,
|
||||
sizeof(emote),
|
||||
"%s",
|
||||
drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "Oh no!" : "Om-nom-nom!");
|
||||
snprintf(header, sizeof(header), "%s", "Consumption is");
|
||||
snprintf(
|
||||
value,
|
||||
sizeof(value),
|
||||
"%ld %s",
|
||||
drain_current,
|
||||
drain_current > HIGH_DRAIN_CURRENT_THRESHOLD ? "mA!" : "mA");
|
||||
} else if(drain_current != 0) {
|
||||
snprintf(header, 20, "...");
|
||||
} else if(data->charging_voltage < 4.2) {
|
||||
// Non-default battery charging limit, mention it
|
||||
snprintf(emote, sizeof(emote), "Charged!");
|
||||
snprintf(header, sizeof(header), "Limited to");
|
||||
snprintf(
|
||||
value,
|
||||
sizeof(value),
|
||||
"%lu.%luV",
|
||||
(uint32_t)(data->charging_voltage),
|
||||
(uint32_t)(data->charging_voltage * 10) % 10);
|
||||
} else {
|
||||
snprintf(header, sizeof(header), "Charged!");
|
||||
}
|
||||
|
||||
canvas_draw_str_aligned(canvas, 92, y + 3, AlignCenter, AlignCenter, emote);
|
||||
canvas_draw_str_aligned(canvas, 92, y + 15, AlignCenter, AlignCenter, header);
|
||||
canvas_draw_str_aligned(canvas, 92, y + 27, AlignCenter, AlignCenter, value);
|
||||
};
|
||||
|
||||
static void battery_info_draw_callback(Canvas* canvas, void* context) {
|
||||
furi_assert(context);
|
||||
BatteryInfoModel* model = context;
|
||||
|
||||
canvas_clear(canvas);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
draw_battery(canvas, model, 0, 5);
|
||||
|
||||
char batt_level[10];
|
||||
char temperature[10];
|
||||
char voltage[10];
|
||||
char health[10];
|
||||
|
||||
snprintf(batt_level, sizeof(batt_level), "%lu%%", (uint32_t)model->charge);
|
||||
snprintf(temperature, sizeof(temperature), "%lu C", (uint32_t)model->gauge_temperature);
|
||||
snprintf(
|
||||
voltage,
|
||||
sizeof(voltage),
|
||||
"%lu.%01lu V",
|
||||
(uint32_t)model->gauge_voltage,
|
||||
(uint32_t)(model->gauge_voltage * 10) % 10UL);
|
||||
snprintf(health, sizeof(health), "%d%%", model->health);
|
||||
|
||||
draw_stat(canvas, 8, 42, &I_Battery_16x16, batt_level);
|
||||
draw_stat(canvas, 40, 42, &I_Temperature_16x16, temperature);
|
||||
draw_stat(canvas, 72, 42, &I_Voltage_16x16, voltage);
|
||||
draw_stat(canvas, 104, 42, &I_Health_16x16, health);
|
||||
}
|
||||
|
||||
BatteryInfo* battery_info_alloc() {
|
||||
BatteryInfo* battery_info = malloc(sizeof(BatteryInfo));
|
||||
battery_info->view = view_alloc();
|
||||
view_set_context(battery_info->view, battery_info);
|
||||
view_allocate_model(battery_info->view, ViewModelTypeLocking, sizeof(BatteryInfoModel));
|
||||
view_set_draw_callback(battery_info->view, battery_info_draw_callback);
|
||||
|
||||
return battery_info;
|
||||
}
|
||||
|
||||
void battery_info_free(BatteryInfo* battery_info) {
|
||||
furi_assert(battery_info);
|
||||
view_free(battery_info->view);
|
||||
free(battery_info);
|
||||
}
|
||||
|
||||
View* battery_info_get_view(BatteryInfo* battery_info) {
|
||||
furi_assert(battery_info);
|
||||
return battery_info->view;
|
||||
}
|
||||
|
||||
void battery_info_set_data(BatteryInfo* battery_info, BatteryInfoModel* data) {
|
||||
furi_assert(battery_info);
|
||||
furi_assert(data);
|
||||
with_view_model(
|
||||
battery_info->view,
|
||||
BatteryInfoModel * model,
|
||||
{ memcpy(model, data, sizeof(BatteryInfoModel)); },
|
||||
true);
|
||||
}
|
||||
23
applications/debug/battery_test_app/views/battery_info.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/view.h>
|
||||
|
||||
typedef struct BatteryInfo BatteryInfo;
|
||||
|
||||
typedef struct {
|
||||
float vbus_voltage;
|
||||
float gauge_voltage;
|
||||
float gauge_current;
|
||||
float gauge_temperature;
|
||||
float charging_voltage;
|
||||
uint8_t charge;
|
||||
uint8_t health;
|
||||
} BatteryInfoModel;
|
||||
|
||||
BatteryInfo* battery_info_alloc();
|
||||
|
||||
void battery_info_free(BatteryInfo* battery_info);
|
||||
|
||||
View* battery_info_get_view(BatteryInfo* battery_info);
|
||||
|
||||
void battery_info_set_data(BatteryInfo* battery_info, BatteryInfoModel* data);
|
||||
@@ -31,9 +31,6 @@ uint32_t bt_debug_start_view(void* context) {
|
||||
BtDebugApp* bt_debug_app_alloc() {
|
||||
BtDebugApp* app = malloc(sizeof(BtDebugApp));
|
||||
|
||||
// Load settings
|
||||
bt_settings_load(&app->settings);
|
||||
|
||||
// Gui
|
||||
app->gui = furi_record_open(RECORD_GUI);
|
||||
|
||||
@@ -105,13 +102,15 @@ int32_t bt_debug_app(void* p) {
|
||||
}
|
||||
|
||||
BtDebugApp* app = bt_debug_app_alloc();
|
||||
// Was bt active?
|
||||
const bool was_active = furi_hal_bt_is_active();
|
||||
// Stop advertising
|
||||
furi_hal_bt_stop_advertising();
|
||||
|
||||
view_dispatcher_run(app->view_dispatcher);
|
||||
|
||||
// Restart advertising
|
||||
if(app->settings.enabled) {
|
||||
if(was_active) {
|
||||
furi_hal_bt_start_advertising();
|
||||
}
|
||||
bt_debug_app_free(app);
|
||||
|
||||
@@ -4,15 +4,14 @@
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/modules/submenu.h>
|
||||
|
||||
#include <dialogs/dialogs.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
#include "views/bt_carrier_test.h"
|
||||
#include "views/bt_packet_test.h"
|
||||
#include <bt/bt_settings.h>
|
||||
|
||||
typedef struct {
|
||||
BtSettings settings;
|
||||
Gui* gui;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Submenu* submenu;
|
||||
|
||||
@@ -5,6 +5,7 @@ App(
|
||||
entry_point="display_test_app",
|
||||
cdefines=["APP_DISPLAY_TEST"],
|
||||
requires=["gui"],
|
||||
fap_libs=["misc"],
|
||||
stack_size=1 * 1024,
|
||||
order=120,
|
||||
fap_category="Debug",
|
||||
|
||||
@@ -91,7 +91,6 @@ static void display_test_reload_config(DisplayTest* instance) {
|
||||
instance->config_contrast,
|
||||
instance->config_regulation_ratio,
|
||||
instance->config_bias);
|
||||
gui_update(instance->gui);
|
||||
}
|
||||
|
||||
static void display_config_set_bias(VariableItem* item) {
|
||||
|
||||
@@ -44,7 +44,11 @@ bool rpc_debug_app_scene_input_error_code_on_event(void* context, SceneManagerEv
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == RpcDebugAppCustomEventInputErrorCode) {
|
||||
rpc_system_app_set_error_code(app->rpc, (uint32_t)atol(app->text_store));
|
||||
char* end;
|
||||
int error_code = strtol(app->text_store, &end, 10);
|
||||
if(!*end) {
|
||||
rpc_system_app_set_error_code(app->rpc, error_code);
|
||||
}
|
||||
scene_manager_previous_scene(app->scene_manager);
|
||||
consumed = true;
|
||||
}
|
||||
|
||||
@@ -466,6 +466,10 @@ static void mf_classic_generator_test(uint8_t uid_len, MfClassicType type) {
|
||||
nfc_device_free(nfc_keys);
|
||||
}
|
||||
|
||||
MU_TEST(mf_mini_file_test) {
|
||||
mf_classic_generator_test(4, MfClassicTypeMini);
|
||||
}
|
||||
|
||||
MU_TEST(mf_classic_1k_4b_file_test) {
|
||||
mf_classic_generator_test(4, MfClassicType1k);
|
||||
}
|
||||
@@ -486,6 +490,7 @@ MU_TEST_SUITE(nfc) {
|
||||
nfc_test_alloc();
|
||||
|
||||
MU_RUN_TEST(nfca_file_test);
|
||||
MU_RUN_TEST(mf_mini_file_test);
|
||||
MU_RUN_TEST(mf_classic_1k_4b_file_test);
|
||||
MU_RUN_TEST(mf_classic_4k_4b_file_test);
|
||||
MU_RUN_TEST(mf_classic_1k_7b_file_test);
|
||||
|
||||
@@ -54,6 +54,7 @@ static const DuckyKey ducky_keys[] = {
|
||||
{"ALT-SHIFT", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_SHIFT},
|
||||
{"ALT-GUI", KEY_MOD_LEFT_ALT | KEY_MOD_LEFT_GUI},
|
||||
{"GUI-SHIFT", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_SHIFT},
|
||||
{"GUI-CTRL", KEY_MOD_LEFT_GUI | KEY_MOD_LEFT_CTRL},
|
||||
|
||||
{"CTRL", KEY_MOD_LEFT_CTRL},
|
||||
{"CONTROL", KEY_MOD_LEFT_CTRL},
|
||||
|
||||
@@ -392,8 +392,8 @@ int32_t snake_game_app(void* p) {
|
||||
release_mutex(&state_mutex, snake_state);
|
||||
}
|
||||
|
||||
// Wait for all notifications to be played and return backlight to normal state
|
||||
notification_message_block(notification, &sequence_display_backlight_enforce_auto);
|
||||
// Return backlight to normal state
|
||||
notification_message(notification, &sequence_display_backlight_enforce_auto);
|
||||
|
||||
furi_timer_free(timer);
|
||||
view_port_enabled_set(view_port, false);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#define WS_VERSION_APP "0.6.1"
|
||||
#define WS_VERSION_APP "0.7"
|
||||
#define WS_DEVELOPED "SkorP"
|
||||
#define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
|
||||
|
||||
|
||||
329
applications/plugins/weather_station/protocols/lacrosse_tx.c
Normal file
@@ -0,0 +1,329 @@
|
||||
#include "lacrosse_tx.h"
|
||||
|
||||
#define TAG "WSProtocolLaCrosse_TX"
|
||||
|
||||
/*
|
||||
* Help
|
||||
* https://github.com/merbanan/rtl_433/blob/master/src/devices/lacrosse.c
|
||||
*
|
||||
*
|
||||
* LaCrosse TX 433 Mhz Temperature and Humidity Sensors.
|
||||
* - Tested: TX-7U and TX-6U (Temperature only)
|
||||
* - Not Tested but should work: TX-3, TX-4
|
||||
* - also TFA Dostmann 30.3120.90 sensor (for e.g. 35.1018.06 (WS-9015) station)
|
||||
* - also TFA Dostmann 30.3121 sensor
|
||||
* Protocol Documentation: http://www.f6fbb.org/domo/sensors/tx3_th.php
|
||||
* Message is 44 bits, 11 x 4 bit nybbles:
|
||||
* [00] [cnt = 10] [type] [addr] [addr + parity] [v1] [v2] [v3] [iv1] [iv2] [check]
|
||||
* Notes:
|
||||
* - Zero Pulses are longer (1,400 uS High, 1,000 uS Low) = 2,400 uS
|
||||
* - One Pulses are shorter ( 550 uS High, 1,000 uS Low) = 1,600 uS
|
||||
* - Sensor id changes when the battery is changed
|
||||
* - Primary Value are BCD with one decimal place: vvv = 12.3
|
||||
* - Secondary value is integer only intval = 12, seems to be a repeat of primary
|
||||
* This may actually be an additional data check because the 4 bit checksum
|
||||
* and parity bit is pretty week at detecting errors.
|
||||
* - Temperature is in Celsius with 50.0 added (to handle negative values)
|
||||
* - Humidity values appear to be integer precision, decimal always 0.
|
||||
* - There is a 4 bit checksum and a parity bit covering the three digit value
|
||||
* - Parity check for TX-3 and TX-4 might be different.
|
||||
* - Msg sent with one repeat after 30 mS
|
||||
* - Temperature and humidity are sent as separate messages
|
||||
* - Frequency for each sensor may be could be off by as much as 50-75 khz
|
||||
* - LaCrosse Sensors in other frequency ranges (915 Mhz) use FSK not OOK
|
||||
* so they can't be decoded by rtl_433 currently.
|
||||
* - Temperature and Humidity are sent in different messages bursts.
|
||||
*/
|
||||
|
||||
#define LACROSSE_TX_GAP 1000
|
||||
#define LACROSSE_TX_BIT_SIZE 44
|
||||
#define LACROSSE_TX_SUNC_PATTERN 0x0A000000000
|
||||
#define LACROSSE_TX_SUNC_MASK 0x0F000000000
|
||||
#define LACROSSE_TX_MSG_TYPE_TEMP 0x00
|
||||
#define LACROSSE_TX_MSG_TYPE_HUM 0x0E
|
||||
|
||||
static const SubGhzBlockConst ws_protocol_lacrosse_tx_const = {
|
||||
.te_short = 550,
|
||||
.te_long = 1300,
|
||||
.te_delta = 120,
|
||||
.min_count_bit_for_found = 40,
|
||||
};
|
||||
|
||||
struct WSProtocolDecoderLaCrosse_TX {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
WSBlockGeneric generic;
|
||||
|
||||
uint16_t header_count;
|
||||
};
|
||||
|
||||
struct WSProtocolEncoderLaCrosse_TX {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
WSBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
LaCrosse_TXDecoderStepReset = 0,
|
||||
LaCrosse_TXDecoderStepCheckPreambule,
|
||||
LaCrosse_TXDecoderStepSaveDuration,
|
||||
LaCrosse_TXDecoderStepCheckDuration,
|
||||
} LaCrosse_TXDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder ws_protocol_lacrosse_tx_decoder = {
|
||||
.alloc = ws_protocol_decoder_lacrosse_tx_alloc,
|
||||
.free = ws_protocol_decoder_lacrosse_tx_free,
|
||||
|
||||
.feed = ws_protocol_decoder_lacrosse_tx_feed,
|
||||
.reset = ws_protocol_decoder_lacrosse_tx_reset,
|
||||
|
||||
.get_hash_data = ws_protocol_decoder_lacrosse_tx_get_hash_data,
|
||||
.serialize = ws_protocol_decoder_lacrosse_tx_serialize,
|
||||
.deserialize = ws_protocol_decoder_lacrosse_tx_deserialize,
|
||||
.get_string = ws_protocol_decoder_lacrosse_tx_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder ws_protocol_lacrosse_tx_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol ws_protocol_lacrosse_tx = {
|
||||
.name = WS_PROTOCOL_LACROSSE_TX_NAME,
|
||||
.type = SubGhzProtocolWeatherStation,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
|
||||
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||
|
||||
.decoder = &ws_protocol_lacrosse_tx_decoder,
|
||||
.encoder = &ws_protocol_lacrosse_tx_encoder,
|
||||
};
|
||||
|
||||
void* ws_protocol_decoder_lacrosse_tx_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
WSProtocolDecoderLaCrosse_TX* instance = malloc(sizeof(WSProtocolDecoderLaCrosse_TX));
|
||||
instance->base.protocol = &ws_protocol_lacrosse_tx;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_lacrosse_tx_free(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderLaCrosse_TX* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_lacrosse_tx_reset(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderLaCrosse_TX* instance = context;
|
||||
instance->header_count = 0;
|
||||
instance->decoder.parser_step = LaCrosse_TXDecoderStepReset;
|
||||
}
|
||||
|
||||
static bool ws_protocol_lacrosse_tx_check_crc(WSProtocolDecoderLaCrosse_TX* instance) {
|
||||
if(!instance->decoder.decode_data) return false;
|
||||
uint8_t msg[] = {
|
||||
(instance->decoder.decode_data >> 36) & 0x0F,
|
||||
(instance->decoder.decode_data >> 32) & 0x0F,
|
||||
(instance->decoder.decode_data >> 28) & 0x0F,
|
||||
(instance->decoder.decode_data >> 24) & 0x0F,
|
||||
(instance->decoder.decode_data >> 20) & 0x0F,
|
||||
(instance->decoder.decode_data >> 16) & 0x0F,
|
||||
(instance->decoder.decode_data >> 12) & 0x0F,
|
||||
(instance->decoder.decode_data >> 8) & 0x0F,
|
||||
(instance->decoder.decode_data >> 4) & 0x0F};
|
||||
|
||||
uint8_t crc = subghz_protocol_blocks_add_bytes(msg, 9);
|
||||
return ((crc & 0x0F) == ((instance->decoder.decode_data) & 0x0F));
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a WSBlockGeneric* instance
|
||||
*/
|
||||
static void ws_protocol_lacrosse_tx_remote_controller(WSBlockGeneric* instance) {
|
||||
uint8_t msg_type = (instance->data >> 32) & 0x0F;
|
||||
instance->id = (((instance->data >> 28) & 0x0F) << 3) | (((instance->data >> 24) & 0x0F) >> 1);
|
||||
|
||||
float msg_value = (float)((instance->data >> 20) & 0x0F) * 10.0f +
|
||||
(float)((instance->data >> 16) & 0x0F) +
|
||||
(float)((instance->data >> 12) & 0x0F) * 0.1f;
|
||||
|
||||
if(msg_type == LACROSSE_TX_MSG_TYPE_TEMP) { //-V1051
|
||||
instance->temp = msg_value - 50.0f;
|
||||
instance->humidity = WS_NO_HUMIDITY;
|
||||
} else if(msg_type == LACROSSE_TX_MSG_TYPE_HUM) {
|
||||
//ToDo for verification, records are needed with sensors maintaining temperature and temperature for this standard
|
||||
instance->humidity = (uint8_t)msg_value;
|
||||
} else {
|
||||
furi_crash("WS: WSProtocolLaCrosse_TX incorrect msg_type.");
|
||||
}
|
||||
|
||||
instance->btn = WS_NO_BTN;
|
||||
instance->battery_low = WS_NO_BATT;
|
||||
instance->channel = WS_NO_CHANNEL;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_lacrosse_tx_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderLaCrosse_TX* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case LaCrosse_TXDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, LACROSSE_TX_GAP) <
|
||||
ws_protocol_lacrosse_tx_const.te_delta * 2)) {
|
||||
instance->decoder.parser_step = LaCrosse_TXDecoderStepCheckPreambule;
|
||||
instance->header_count = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case LaCrosse_TXDecoderStepCheckPreambule:
|
||||
|
||||
if(level) {
|
||||
if((DURATION_DIFF(duration, ws_protocol_lacrosse_tx_const.te_short) <
|
||||
ws_protocol_lacrosse_tx_const.te_delta) &&
|
||||
(instance->header_count > 1)) {
|
||||
instance->decoder.parser_step = LaCrosse_TXDecoderStepCheckDuration;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.te_last = duration;
|
||||
} else if(duration > (ws_protocol_lacrosse_tx_const.te_long * 2)) {
|
||||
instance->decoder.parser_step = LaCrosse_TXDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(DURATION_DIFF(duration, LACROSSE_TX_GAP) <
|
||||
ws_protocol_lacrosse_tx_const.te_delta * 2) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count++;
|
||||
} else {
|
||||
instance->decoder.parser_step = LaCrosse_TXDecoderStepReset;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case LaCrosse_TXDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = LaCrosse_TXDecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = LaCrosse_TXDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case LaCrosse_TXDecoderStepCheckDuration:
|
||||
|
||||
if(!level) {
|
||||
if(duration > LACROSSE_TX_GAP * 3) {
|
||||
if(DURATION_DIFF(
|
||||
instance->decoder.te_last, ws_protocol_lacrosse_tx_const.te_short) <
|
||||
ws_protocol_lacrosse_tx_const.te_delta) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = LaCrosse_TXDecoderStepSaveDuration;
|
||||
} else if(
|
||||
DURATION_DIFF(
|
||||
instance->decoder.te_last, ws_protocol_lacrosse_tx_const.te_long) <
|
||||
ws_protocol_lacrosse_tx_const.te_delta) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = LaCrosse_TXDecoderStepSaveDuration;
|
||||
}
|
||||
if((instance->decoder.decode_data & LACROSSE_TX_SUNC_MASK) ==
|
||||
LACROSSE_TX_SUNC_PATTERN) {
|
||||
if(ws_protocol_lacrosse_tx_check_crc(instance)) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = LACROSSE_TX_BIT_SIZE;
|
||||
ws_protocol_lacrosse_tx_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;
|
||||
instance->header_count = 0;
|
||||
instance->decoder.parser_step = LaCrosse_TXDecoderStepReset;
|
||||
break;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, ws_protocol_lacrosse_tx_const.te_short) <
|
||||
ws_protocol_lacrosse_tx_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, LACROSSE_TX_GAP) <
|
||||
ws_protocol_lacrosse_tx_const.te_delta * 2)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = LaCrosse_TXDecoderStepSaveDuration;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, ws_protocol_lacrosse_tx_const.te_long) <
|
||||
ws_protocol_lacrosse_tx_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, LACROSSE_TX_GAP) <
|
||||
ws_protocol_lacrosse_tx_const.te_delta * 2)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = LaCrosse_TXDecoderStepSaveDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = LaCrosse_TXDecoderStepReset;
|
||||
}
|
||||
|
||||
} else {
|
||||
instance->decoder.parser_step = LaCrosse_TXDecoderStepReset;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ws_protocol_decoder_lacrosse_tx_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderLaCrosse_TX* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
bool ws_protocol_decoder_lacrosse_tx_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderLaCrosse_TX* instance = context;
|
||||
return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
bool ws_protocol_decoder_lacrosse_tx_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderLaCrosse_TX* instance = context;
|
||||
bool ret = false;
|
||||
do {
|
||||
if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
|
||||
break;
|
||||
}
|
||||
if(instance->generic.data_count_bit !=
|
||||
ws_protocol_lacrosse_tx_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_lacrosse_tx_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderLaCrosse_TX* 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 >> 32),
|
||||
(uint32_t)(instance->generic.data),
|
||||
instance->generic.id,
|
||||
instance->generic.channel,
|
||||
instance->generic.battery_low,
|
||||
(double)instance->generic.temp,
|
||||
instance->generic.humidity);
|
||||
}
|
||||
79
applications/plugins/weather_station/protocols/lacrosse_tx.h
Normal file
@@ -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_LACROSSE_TX_NAME "LaCrosse_TX"
|
||||
|
||||
typedef struct WSProtocolDecoderLaCrosse_TX WSProtocolDecoderLaCrosse_TX;
|
||||
typedef struct WSProtocolEncoderLaCrosse_TX WSProtocolEncoderLaCrosse_TX;
|
||||
|
||||
extern const SubGhzProtocolDecoder ws_protocol_lacrosse_tx_decoder;
|
||||
extern const SubGhzProtocolEncoder ws_protocol_lacrosse_tx_encoder;
|
||||
extern const SubGhzProtocol ws_protocol_lacrosse_tx;
|
||||
|
||||
/**
|
||||
* Allocate WSProtocolDecoderLaCrosse_TX.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return WSProtocolDecoderLaCrosse_TX* pointer to a WSProtocolDecoderLaCrosse_TX instance
|
||||
*/
|
||||
void* ws_protocol_decoder_lacrosse_tx_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free WSProtocolDecoderLaCrosse_TX.
|
||||
* @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance
|
||||
*/
|
||||
void ws_protocol_decoder_lacrosse_tx_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder WSProtocolDecoderLaCrosse_TX.
|
||||
* @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance
|
||||
*/
|
||||
void ws_protocol_decoder_lacrosse_tx_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void ws_protocol_decoder_lacrosse_tx_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t ws_protocol_decoder_lacrosse_tx_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data WSProtocolDecoderLaCrosse_TX.
|
||||
* @param context Pointer to a WSProtocolDecoderLaCrosse_TX 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_lacrosse_tx_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data WSProtocolDecoderLaCrosse_TX.
|
||||
* @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return true On success
|
||||
*/
|
||||
bool ws_protocol_decoder_lacrosse_tx_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a WSProtocolDecoderLaCrosse_TX instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void ws_protocol_decoder_lacrosse_tx_get_string(void* context, FuriString* output);
|
||||
@@ -8,6 +8,7 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = {
|
||||
&ws_protocol_gt_wt_03,
|
||||
&ws_protocol_acurite_606tx,
|
||||
&ws_protocol_acurite_609txc,
|
||||
&ws_protocol_lacrosse_tx,
|
||||
&ws_protocol_lacrosse_tx141thbv2,
|
||||
&ws_protocol_oregon2,
|
||||
&ws_protocol_acurite_592txr,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "gt_wt_03.h"
|
||||
#include "acurite_606tx.h"
|
||||
#include "acurite_609txc.h"
|
||||
#include "lacrosse_tx.h"
|
||||
#include "lacrosse_tx141thbv2.h"
|
||||
#include "oregon2.h"
|
||||
#include "acurite_592txr.h"
|
||||
|
||||
@@ -5,6 +5,10 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
bool enabled;
|
||||
} BtSettings;
|
||||
@@ -12,3 +16,7 @@ typedef struct {
|
||||
bool bt_settings_load(BtSettings* bt_settings);
|
||||
|
||||
bool bt_settings_save(BtSettings* bt_settings);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <stm32_adafruit_sd.h>
|
||||
|
||||
#include <cli/cli.h>
|
||||
#include <lib/toolbox/args.h>
|
||||
@@ -60,17 +61,28 @@ static void storage_cli_info(Cli* cli, FuriString* path) {
|
||||
}
|
||||
} else if(furi_string_cmp_str(path, STORAGE_EXT_PATH_PREFIX) == 0) {
|
||||
SDInfo sd_info;
|
||||
SD_CID sd_cid;
|
||||
FS_Error error = storage_sd_info(api, &sd_info);
|
||||
BSP_SD_GetCIDRegister(&sd_cid);
|
||||
|
||||
if(error != FSE_OK) {
|
||||
storage_cli_print_error(error);
|
||||
} else {
|
||||
printf(
|
||||
"Label: %s\r\nType: %s\r\n%luKiB total\r\n%luKiB free\r\n",
|
||||
"Label: %s\r\nType: %s\r\n%luKiB total\r\n%luKiB free\r\n"
|
||||
"%02x%2.2s %5.5s %i.%i\r\nSN:%04lx %02i/%i\r\n",
|
||||
sd_info.label,
|
||||
sd_api_get_fs_type_text(sd_info.fs_type),
|
||||
sd_info.kb_total,
|
||||
sd_info.kb_free);
|
||||
sd_info.kb_free,
|
||||
sd_cid.ManufacturerID,
|
||||
sd_cid.OEM_AppliID,
|
||||
sd_cid.ProdName,
|
||||
sd_cid.ProdRev >> 4,
|
||||
sd_cid.ProdRev & 0xf,
|
||||
sd_cid.ProdSN,
|
||||
sd_cid.ManufactMonth,
|
||||
sd_cid.ManufactYear + 2000);
|
||||
}
|
||||
} else {
|
||||
storage_cli_print_usage();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "../storage_settings.h"
|
||||
#include <stm32_adafruit_sd.h>
|
||||
|
||||
static void storage_settings_scene_sd_info_dialog_callback(DialogExResult result, void* context) {
|
||||
StorageSettings* app = context;
|
||||
@@ -11,7 +12,10 @@ void storage_settings_scene_sd_info_on_enter(void* context) {
|
||||
DialogEx* dialog_ex = app->dialog_ex;
|
||||
|
||||
SDInfo sd_info;
|
||||
SD_CID sd_cid;
|
||||
FS_Error sd_status = storage_sd_info(app->fs_api, &sd_info);
|
||||
BSP_SD_GetCIDRegister(&sd_cid);
|
||||
|
||||
scene_manager_set_scene_state(app->scene_manager, StorageSettingsSDInfo, sd_status);
|
||||
|
||||
dialog_ex_set_context(dialog_ex, app);
|
||||
@@ -26,13 +30,22 @@ void storage_settings_scene_sd_info_on_enter(void* context) {
|
||||
} else {
|
||||
furi_string_printf(
|
||||
app->text_string,
|
||||
"Label: %s\nType: %s\n%lu KiB total\n%lu KiB free",
|
||||
"Label: %s\nType: %s\n%lu KiB total\n%lu KiB free\n"
|
||||
"%02X%2.2s %5.5s %i.%i\nSN:%04lX %02i/%i",
|
||||
sd_info.label,
|
||||
sd_api_get_fs_type_text(sd_info.fs_type),
|
||||
sd_info.kb_total,
|
||||
sd_info.kb_free);
|
||||
sd_info.kb_free,
|
||||
sd_cid.ManufacturerID,
|
||||
sd_cid.OEM_AppliID,
|
||||
sd_cid.ProdName,
|
||||
sd_cid.ProdRev >> 4,
|
||||
sd_cid.ProdRev & 0xf,
|
||||
sd_cid.ProdSN,
|
||||
sd_cid.ManufactMonth,
|
||||
sd_cid.ManufactYear + 2000);
|
||||
dialog_ex_set_text(
|
||||
dialog_ex, furi_string_get_cstr(app->text_string), 4, 4, AlignLeft, AlignTop);
|
||||
dialog_ex, furi_string_get_cstr(app->text_string), 4, 1, AlignLeft, AlignTop);
|
||||
}
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, StorageSettingsViewDialogEx);
|
||||
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -67,6 +67,7 @@ Can be combined with a special key command or a single character.
|
||||
|ALT-SHIFT|ALT+SHIFT|
|
||||
|ALT-GUI|ALT+WIN|
|
||||
|GUI-SHIFT|WIN+SHIFT|
|
||||
|GUI-CTRL|WIN+CTRL|
|
||||
|
||||
## String
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
entry,status,name,type,params
|
||||
Version,+,11.7,,
|
||||
Version,+,11.9,,
|
||||
Header,+,applications/services/bt/bt_service/bt.h,,
|
||||
Header,+,applications/services/cli/cli.h,,
|
||||
Header,+,applications/services/cli/cli_vcp.h,,
|
||||
@@ -1408,6 +1408,7 @@ Function,-,furi_hal_vibro_init,void,
|
||||
Function,+,furi_hal_vibro_on,void,_Bool
|
||||
Function,-,furi_init,void,
|
||||
Function,+,furi_kernel_get_tick_frequency,uint32_t,
|
||||
Function,+,furi_kernel_is_irq_or_masked,_Bool,
|
||||
Function,+,furi_kernel_lock,int32_t,
|
||||
Function,+,furi_kernel_restore_lock,int32_t,int32_t
|
||||
Function,+,furi_kernel_unlock,int32_t,
|
||||
@@ -1532,6 +1533,7 @@ Function,+,furi_thread_get_name,const char*,FuriThreadId
|
||||
Function,+,furi_thread_get_return_code,int32_t,FuriThread*
|
||||
Function,+,furi_thread_get_stack_space,uint32_t,FuriThreadId
|
||||
Function,+,furi_thread_get_state,FuriThreadState,FuriThread*
|
||||
Function,+,furi_thread_get_stdout_callback,FuriThreadStdoutWriteCallback,
|
||||
Function,+,furi_thread_is_suspended,_Bool,FuriThreadId
|
||||
Function,+,furi_thread_join,_Bool,FuriThread*
|
||||
Function,+,furi_thread_mark_as_service,void,FuriThread*
|
||||
@@ -1901,7 +1903,7 @@ Function,-,mf_classic_dict_is_key_present,_Bool,"MfClassicDict*, uint8_t*"
|
||||
Function,-,mf_classic_dict_is_key_present_str,_Bool,"MfClassicDict*, FuriString*"
|
||||
Function,-,mf_classic_dict_rewind,_Bool,MfClassicDict*
|
||||
Function,-,mf_classic_emulator,_Bool,"MfClassicEmulator*, FuriHalNfcTxRxContext*"
|
||||
Function,-,mf_classic_get_classic_type,MfClassicType,"int8_t, uint8_t, uint8_t"
|
||||
Function,-,mf_classic_get_classic_type,MfClassicType,"uint8_t, uint8_t, uint8_t"
|
||||
Function,-,mf_classic_get_read_sectors_and_keys,void,"MfClassicData*, uint8_t*, uint8_t*"
|
||||
Function,-,mf_classic_get_sector_by_block,uint8_t,uint8_t
|
||||
Function,-,mf_classic_get_sector_trailer_block_num_by_sector,uint8_t,uint8_t
|
||||
@@ -2087,7 +2089,7 @@ Function,-,posix_memalign,int,"void**, size_t, size_t"
|
||||
Function,-,pow,double,"double, double"
|
||||
Function,-,pow10,double,double
|
||||
Function,-,pow10f,float,float
|
||||
Function,-,power_enable_low_battery_level_notification,void,"Power*, _Bool"
|
||||
Function,+,power_enable_low_battery_level_notification,void,"Power*, _Bool"
|
||||
Function,+,power_get_info,void,"Power*, PowerInfo*"
|
||||
Function,+,power_get_pubsub,FuriPubSub*,Power*
|
||||
Function,+,power_is_battery_healthy,_Bool,Power*
|
||||
|
||||
|
@@ -779,25 +779,10 @@ uint8_t SD_GetCIDRegister(SD_CID* Cid) {
|
||||
Cid->ManufacturerID = CID_Tab[0];
|
||||
|
||||
/* Byte 1 */
|
||||
Cid->OEM_AppliID = CID_Tab[1] << 8;
|
||||
|
||||
/* Byte 2 */
|
||||
Cid->OEM_AppliID |= CID_Tab[2];
|
||||
memcpy(Cid->OEM_AppliID, CID_Tab + 1, 2);
|
||||
|
||||
/* Byte 3 */
|
||||
Cid->ProdName1 = CID_Tab[3] << 24;
|
||||
|
||||
/* Byte 4 */
|
||||
Cid->ProdName1 |= CID_Tab[4] << 16;
|
||||
|
||||
/* Byte 5 */
|
||||
Cid->ProdName1 |= CID_Tab[5] << 8;
|
||||
|
||||
/* Byte 6 */
|
||||
Cid->ProdName1 |= CID_Tab[6];
|
||||
|
||||
/* Byte 7 */
|
||||
Cid->ProdName2 = CID_Tab[7];
|
||||
memcpy(Cid->ProdName, CID_Tab + 3, 5);
|
||||
|
||||
/* Byte 8 */
|
||||
Cid->ProdRev = CID_Tab[8];
|
||||
@@ -815,11 +800,12 @@ uint8_t SD_GetCIDRegister(SD_CID* Cid) {
|
||||
Cid->ProdSN |= CID_Tab[12];
|
||||
|
||||
/* Byte 13 */
|
||||
Cid->Reserved1 |= (CID_Tab[13] & 0xF0) >> 4;
|
||||
Cid->ManufactDate = (CID_Tab[13] & 0x0F) << 8;
|
||||
Cid->Reserved1 = (CID_Tab[13] & 0xF0) >> 4;
|
||||
Cid->ManufactYear = (CID_Tab[13] & 0x0F) << 4;
|
||||
|
||||
/* Byte 14 */
|
||||
Cid->ManufactDate |= CID_Tab[14];
|
||||
Cid->ManufactYear |= (CID_Tab[14] & 0xF0) >> 4;
|
||||
Cid->ManufactMonth = (CID_Tab[14] & 0x0F);
|
||||
|
||||
/* Byte 15 */
|
||||
Cid->CID_CRC = (CID_Tab[15] & 0xFE) >> 1;
|
||||
@@ -837,6 +823,21 @@ uint8_t SD_GetCIDRegister(SD_CID* Cid) {
|
||||
return retr;
|
||||
}
|
||||
|
||||
uint8_t BSP_SD_GetCIDRegister(SD_CID* Cid) {
|
||||
uint8_t retr = BSP_SD_ERROR;
|
||||
|
||||
/* Slow speed init */
|
||||
furi_hal_spi_acquire(&furi_hal_spi_bus_handle_sd_slow);
|
||||
furi_hal_sd_spi_handle = &furi_hal_spi_bus_handle_sd_slow;
|
||||
|
||||
memset(Cid, 0, sizeof(SD_CID));
|
||||
retr = SD_GetCIDRegister(Cid);
|
||||
|
||||
furi_hal_sd_spi_handle = NULL;
|
||||
furi_hal_spi_release(&furi_hal_spi_bus_handle_sd_slow);
|
||||
return retr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sends 5 bytes command to the SD card and get response
|
||||
* @param Cmd: The user expected command to send to SD card.
|
||||
|
||||
@@ -133,16 +133,16 @@ typedef struct {
|
||||
* @brief Card Identification Data: CID Register
|
||||
*/
|
||||
typedef struct {
|
||||
__IO uint8_t ManufacturerID; /* ManufacturerID */
|
||||
__IO uint16_t OEM_AppliID; /* OEM/Application ID */
|
||||
__IO uint32_t ProdName1; /* Product Name part1 */
|
||||
__IO uint8_t ProdName2; /* Product Name part2*/
|
||||
__IO uint8_t ProdRev; /* Product Revision */
|
||||
__IO uint32_t ProdSN; /* Product Serial Number */
|
||||
__IO uint8_t Reserved1; /* Reserved1 */
|
||||
__IO uint16_t ManufactDate; /* Manufacturing Date */
|
||||
__IO uint8_t CID_CRC; /* CID CRC */
|
||||
__IO uint8_t Reserved2; /* always 1 */
|
||||
uint8_t ManufacturerID; /* ManufacturerID */
|
||||
char OEM_AppliID[2]; /* OEM/Application ID */
|
||||
char ProdName[5]; /* Product Name */
|
||||
uint8_t ProdRev; /* Product Revision */
|
||||
uint32_t ProdSN; /* Product Serial Number */
|
||||
uint8_t Reserved1; /* Reserved1 */
|
||||
uint8_t ManufactYear; /* Manufacturing Year */
|
||||
uint8_t ManufactMonth; /* Manufacturing Month */
|
||||
uint8_t CID_CRC; /* CID CRC */
|
||||
uint8_t Reserved2; /* always 1 */
|
||||
} SD_CID;
|
||||
|
||||
/**
|
||||
@@ -207,6 +207,7 @@ uint8_t
|
||||
uint8_t BSP_SD_Erase(uint32_t StartAddr, uint32_t EndAddr);
|
||||
uint8_t BSP_SD_GetCardState(void);
|
||||
uint8_t BSP_SD_GetCardInfo(SD_CardInfo* pCardInfo);
|
||||
uint8_t BSP_SD_GetCIDRegister(SD_CID* Cid);
|
||||
|
||||
/* Link functions for SD Card peripheral*/
|
||||
void SD_SPI_Slow_Init(void);
|
||||
|
||||
@@ -52,30 +52,6 @@ extern "C" {
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline bool furi_is_irq_context() {
|
||||
bool irq = false;
|
||||
BaseType_t state;
|
||||
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
/* Called from interrupt context */
|
||||
irq = true;
|
||||
} else {
|
||||
/* Get FreeRTOS scheduler state */
|
||||
state = xTaskGetSchedulerState();
|
||||
|
||||
if(state != taskSCHEDULER_NOT_STARTED) {
|
||||
/* Scheduler was started */
|
||||
if(FURI_IS_IRQ_MASKED()) {
|
||||
/* Interrupts are masked */
|
||||
irq = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return context, 0: thread context, 1: IRQ context */
|
||||
return (irq);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -7,8 +7,32 @@
|
||||
|
||||
#include CMSIS_device_header
|
||||
|
||||
bool furi_kernel_is_irq_or_masked() {
|
||||
bool irq = false;
|
||||
BaseType_t state;
|
||||
|
||||
if(FURI_IS_IRQ_MODE()) {
|
||||
/* Called from interrupt context */
|
||||
irq = true;
|
||||
} else {
|
||||
/* Get FreeRTOS scheduler state */
|
||||
state = xTaskGetSchedulerState();
|
||||
|
||||
if(state != taskSCHEDULER_NOT_STARTED) {
|
||||
/* Scheduler was started */
|
||||
if(FURI_IS_IRQ_MASKED()) {
|
||||
/* Interrupts are masked */
|
||||
irq = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Return context, 0: thread context, 1: IRQ context */
|
||||
return (irq);
|
||||
}
|
||||
|
||||
int32_t furi_kernel_lock() {
|
||||
furi_assert(!furi_is_irq_context());
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
|
||||
int32_t lock;
|
||||
|
||||
@@ -33,7 +57,7 @@ int32_t furi_kernel_lock() {
|
||||
}
|
||||
|
||||
int32_t furi_kernel_unlock() {
|
||||
furi_assert(!furi_is_irq_context());
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
|
||||
int32_t lock;
|
||||
|
||||
@@ -63,7 +87,7 @@ int32_t furi_kernel_unlock() {
|
||||
}
|
||||
|
||||
int32_t furi_kernel_restore_lock(int32_t lock) {
|
||||
furi_assert(!furi_is_irq_context());
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
|
||||
switch(xTaskGetSchedulerState()) {
|
||||
case taskSCHEDULER_SUSPENDED:
|
||||
@@ -99,7 +123,7 @@ uint32_t furi_kernel_get_tick_frequency() {
|
||||
}
|
||||
|
||||
void furi_delay_tick(uint32_t ticks) {
|
||||
furi_assert(!furi_is_irq_context());
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
if(ticks == 0U) {
|
||||
taskYIELD();
|
||||
} else {
|
||||
@@ -108,7 +132,7 @@ void furi_delay_tick(uint32_t ticks) {
|
||||
}
|
||||
|
||||
FuriStatus furi_delay_until_tick(uint32_t tick) {
|
||||
furi_assert(!furi_is_irq_context());
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
|
||||
TickType_t tcnt, delay;
|
||||
FuriStatus stat;
|
||||
@@ -137,7 +161,7 @@ FuriStatus furi_delay_until_tick(uint32_t tick) {
|
||||
uint32_t furi_get_tick() {
|
||||
TickType_t ticks;
|
||||
|
||||
if(furi_is_irq_context() != 0U) {
|
||||
if(furi_kernel_is_irq_or_masked() != 0U) {
|
||||
ticks = xTaskGetTickCountFromISR();
|
||||
} else {
|
||||
ticks = xTaskGetTickCount();
|
||||
|
||||
@@ -10,19 +10,42 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Check if CPU is in IRQ or kernel running and IRQ is masked
|
||||
*
|
||||
* Originally this primitive was born as a workaround for FreeRTOS kernel primitives shenanigans with PRIMASK.
|
||||
*
|
||||
* Meaningful use cases are:
|
||||
*
|
||||
* - When kernel is started and you want to ensure that you are not in IRQ or IRQ is not masked(like in critical section)
|
||||
* - When kernel is not started and you want to make sure that you are not in IRQ mode, ignoring PRIMASK.
|
||||
*
|
||||
* As you can see there will be edge case when kernel is not started and PRIMASK is not 0 that may cause some funky behavior.
|
||||
* Most likely it will happen after kernel primitives being used, but control not yet passed to kernel.
|
||||
* It's up to you to figure out if it is safe for your code or not.
|
||||
*
|
||||
* @return true if CPU is in IRQ or kernel running and IRQ is masked
|
||||
*/
|
||||
bool furi_kernel_is_irq_or_masked();
|
||||
|
||||
/** Lock kernel, pause process scheduling
|
||||
*
|
||||
* @warning This should never be called in interrupt request context.
|
||||
*
|
||||
* @return previous lock state(0 - unlocked, 1 - locked)
|
||||
*/
|
||||
int32_t furi_kernel_lock();
|
||||
|
||||
/** Unlock kernel, resume process scheduling
|
||||
*
|
||||
* @warning This should never be called in interrupt request context.
|
||||
*
|
||||
* @return previous lock state(0 - unlocked, 1 - locked)
|
||||
*/
|
||||
int32_t furi_kernel_unlock();
|
||||
|
||||
/** Restore kernel lock state
|
||||
*
|
||||
* @warning This should never be called in interrupt request context.
|
||||
*
|
||||
* @param[in] lock The lock state
|
||||
*
|
||||
@@ -37,7 +60,9 @@ int32_t furi_kernel_restore_lock(int32_t lock);
|
||||
uint32_t furi_kernel_get_tick_frequency();
|
||||
|
||||
/** Delay execution
|
||||
*
|
||||
*
|
||||
* @warning This should never be called in interrupt request context.
|
||||
*
|
||||
* Also keep in mind delay is aliased to scheduler timer intervals.
|
||||
*
|
||||
* @param[in] ticks The ticks count to pause
|
||||
@@ -45,6 +70,8 @@ uint32_t furi_kernel_get_tick_frequency();
|
||||
void furi_delay_tick(uint32_t ticks);
|
||||
|
||||
/** Delay until tick
|
||||
*
|
||||
* @warning This should never be called in interrupt request context.
|
||||
*
|
||||
* @param[in] ticks The tick until which kerel should delay task execution
|
||||
*
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#include "kernel.h"
|
||||
#include "message_queue.h"
|
||||
#include "core/common_defines.h"
|
||||
#include <FreeRTOS.h>
|
||||
#include <queue.h>
|
||||
#include "check.h"
|
||||
|
||||
FuriMessageQueue* furi_message_queue_alloc(uint32_t msg_count, uint32_t msg_size) {
|
||||
furi_assert((furi_is_irq_context() == 0U) && (msg_count > 0U) && (msg_size > 0U));
|
||||
furi_assert((furi_kernel_is_irq_or_masked() == 0U) && (msg_count > 0U) && (msg_size > 0U));
|
||||
|
||||
QueueHandle_t handle = xQueueCreate(msg_count, msg_size);
|
||||
furi_check(handle);
|
||||
@@ -14,7 +14,7 @@ FuriMessageQueue* furi_message_queue_alloc(uint32_t msg_count, uint32_t msg_size
|
||||
}
|
||||
|
||||
void furi_message_queue_free(FuriMessageQueue* instance) {
|
||||
furi_assert(furi_is_irq_context() == 0U);
|
||||
furi_assert(furi_kernel_is_irq_or_masked() == 0U);
|
||||
furi_assert(instance);
|
||||
|
||||
vQueueDelete((QueueHandle_t)instance);
|
||||
@@ -28,7 +28,7 @@ FuriStatus
|
||||
|
||||
stat = FuriStatusOk;
|
||||
|
||||
if(furi_is_irq_context() != 0U) {
|
||||
if(furi_kernel_is_irq_or_masked() != 0U) {
|
||||
if((hQueue == NULL) || (msg_ptr == NULL) || (timeout != 0U)) {
|
||||
stat = FuriStatusErrorParameter;
|
||||
} else {
|
||||
@@ -65,7 +65,7 @@ FuriStatus furi_message_queue_get(FuriMessageQueue* instance, void* msg_ptr, uin
|
||||
|
||||
stat = FuriStatusOk;
|
||||
|
||||
if(furi_is_irq_context() != 0U) {
|
||||
if(furi_kernel_is_irq_or_masked() != 0U) {
|
||||
if((hQueue == NULL) || (msg_ptr == NULL) || (timeout != 0U)) {
|
||||
stat = FuriStatusErrorParameter;
|
||||
} else {
|
||||
@@ -131,7 +131,7 @@ uint32_t furi_message_queue_get_count(FuriMessageQueue* instance) {
|
||||
|
||||
if(hQueue == NULL) {
|
||||
count = 0U;
|
||||
} else if(furi_is_irq_context() != 0U) {
|
||||
} else if(furi_kernel_is_irq_or_masked() != 0U) {
|
||||
count = uxQueueMessagesWaitingFromISR(hQueue);
|
||||
} else {
|
||||
count = uxQueueMessagesWaiting(hQueue);
|
||||
@@ -148,7 +148,7 @@ uint32_t furi_message_queue_get_space(FuriMessageQueue* instance) {
|
||||
|
||||
if(mq == NULL) {
|
||||
space = 0U;
|
||||
} else if(furi_is_irq_context() != 0U) {
|
||||
} else if(furi_kernel_is_irq_or_masked() != 0U) {
|
||||
isrm = taskENTER_CRITICAL_FROM_ISR();
|
||||
|
||||
/* space = pxQueue->uxLength - pxQueue->uxMessagesWaiting; */
|
||||
@@ -167,7 +167,7 @@ FuriStatus furi_message_queue_reset(FuriMessageQueue* instance) {
|
||||
QueueHandle_t hQueue = (QueueHandle_t)instance;
|
||||
FuriStatus stat;
|
||||
|
||||
if(furi_is_irq_context() != 0U) {
|
||||
if(furi_kernel_is_irq_or_masked() != 0U) {
|
||||
stat = FuriStatusErrorISR;
|
||||
} else if(hQueue == NULL) {
|
||||
stat = FuriStatusErrorParameter;
|
||||
|
||||
@@ -530,6 +530,12 @@ bool furi_thread_set_stdout_callback(FuriThreadStdoutWriteCallback callback) {
|
||||
return true;
|
||||
}
|
||||
|
||||
FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback() {
|
||||
FuriThread* thread = furi_thread_get_current();
|
||||
|
||||
return thread->output.write_callback;
|
||||
}
|
||||
|
||||
size_t furi_thread_stdout_write(const char* data, size_t size) {
|
||||
FuriThread* thread = furi_thread_get_current();
|
||||
|
||||
|
||||
@@ -227,6 +227,12 @@ const char* furi_thread_get_name(FuriThreadId thread_id);
|
||||
|
||||
uint32_t furi_thread_get_stack_space(FuriThreadId thread_id);
|
||||
|
||||
/** Get STDOUT callback for thead
|
||||
*
|
||||
* @return STDOUT callback
|
||||
*/
|
||||
FuriThreadStdoutWriteCallback furi_thread_get_stdout_callback();
|
||||
|
||||
/** Set STDOUT callback for thread
|
||||
*
|
||||
* @param callback callback or NULL to clear
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "memmgr.h"
|
||||
#include "kernel.h"
|
||||
|
||||
#include "core/common_defines.h"
|
||||
#include <FreeRTOS.h>
|
||||
#include <timers.h>
|
||||
|
||||
@@ -27,7 +26,7 @@ static void TimerCallback(TimerHandle_t hTimer) {
|
||||
}
|
||||
|
||||
FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* context) {
|
||||
furi_assert((furi_is_irq_context() == 0U) && (func != NULL));
|
||||
furi_assert((furi_kernel_is_irq_or_masked() == 0U) && (func != NULL));
|
||||
|
||||
TimerHandle_t hTimer;
|
||||
TimerCallback_t* callb;
|
||||
@@ -60,7 +59,7 @@ FuriTimer* furi_timer_alloc(FuriTimerCallback func, FuriTimerType type, void* co
|
||||
}
|
||||
|
||||
void furi_timer_free(FuriTimer* instance) {
|
||||
furi_assert(!furi_is_irq_context());
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
furi_assert(instance);
|
||||
|
||||
TimerHandle_t hTimer = (TimerHandle_t)instance;
|
||||
@@ -82,7 +81,7 @@ void furi_timer_free(FuriTimer* instance) {
|
||||
}
|
||||
|
||||
FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) {
|
||||
furi_assert(!furi_is_irq_context());
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
furi_assert(instance);
|
||||
|
||||
TimerHandle_t hTimer = (TimerHandle_t)instance;
|
||||
@@ -99,7 +98,7 @@ FuriStatus furi_timer_start(FuriTimer* instance, uint32_t ticks) {
|
||||
}
|
||||
|
||||
FuriStatus furi_timer_stop(FuriTimer* instance) {
|
||||
furi_assert(!furi_is_irq_context());
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
furi_assert(instance);
|
||||
|
||||
TimerHandle_t hTimer = (TimerHandle_t)instance;
|
||||
@@ -117,7 +116,7 @@ FuriStatus furi_timer_stop(FuriTimer* instance) {
|
||||
}
|
||||
|
||||
uint32_t furi_timer_is_running(FuriTimer* instance) {
|
||||
furi_assert(!furi_is_irq_context());
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
furi_assert(instance);
|
||||
|
||||
TimerHandle_t hTimer = (TimerHandle_t)instance;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "queue.h"
|
||||
|
||||
void furi_init() {
|
||||
furi_assert(!furi_is_irq_context());
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
furi_assert(xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED);
|
||||
|
||||
furi_log_init();
|
||||
@@ -11,7 +11,7 @@ void furi_init() {
|
||||
}
|
||||
|
||||
void furi_run() {
|
||||
furi_assert(!furi_is_irq_context());
|
||||
furi_assert(!furi_kernel_is_irq_or_masked());
|
||||
furi_assert(xTaskGetSchedulerState() == taskSCHEDULER_NOT_STARTED);
|
||||
|
||||
#if(__ARM_ARCH_7A__ == 0U)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "flipper_application.h"
|
||||
#include "elf/elf_file.h"
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#define TAG "fapp"
|
||||
|
||||
@@ -95,6 +96,15 @@ static int32_t flipper_application_thread(void* context) {
|
||||
elf_file_pre_run(last_loaded_app->elf);
|
||||
int32_t result = elf_file_run(last_loaded_app->elf, context);
|
||||
elf_file_post_run(last_loaded_app->elf);
|
||||
|
||||
// wait until all notifications from RAM are completed
|
||||
NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
const NotificationSequence sequence_empty = {
|
||||
NULL,
|
||||
};
|
||||
notification_message_block(notifications, &sequence_empty);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -136,17 +136,45 @@ LevelDuration protocol_paradox_encoder_yield(ProtocolParadox* protocol) {
|
||||
return level_duration_make(level, duration);
|
||||
};
|
||||
|
||||
static uint8_t protocol_paradox_calculate_checksum(uint8_t fc, uint16_t card_id) {
|
||||
uint8_t card_hi = (card_id >> 8) & 0xff;
|
||||
uint8_t card_lo = card_id & 0xff;
|
||||
|
||||
uint8_t arr[5] = {0, 0, fc, card_hi, card_lo};
|
||||
|
||||
uint8_t manchester[9];
|
||||
|
||||
bit_lib_push_bit(manchester, 9, false);
|
||||
bit_lib_push_bit(manchester, 9, false);
|
||||
bit_lib_push_bit(manchester, 9, false);
|
||||
bit_lib_push_bit(manchester, 9, false);
|
||||
|
||||
for(uint8_t i = 6; i < 40; i += 1) {
|
||||
if(bit_lib_get_bit(arr, i) == 0b1) {
|
||||
bit_lib_push_bit(manchester, 9, true);
|
||||
bit_lib_push_bit(manchester, 9, false);
|
||||
} else {
|
||||
bit_lib_push_bit(manchester, 9, false);
|
||||
bit_lib_push_bit(manchester, 9, true);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t output = bit_lib_crc8(manchester, 9, 0x31, 0x00, true, true, 0x06);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
void protocol_paradox_render_data(ProtocolParadox* protocol, FuriString* result) {
|
||||
uint8_t* decoded_data = protocol->data;
|
||||
uint8_t fc = bit_lib_get_bits(decoded_data, 10, 8);
|
||||
uint16_t card_id = bit_lib_get_bits_16(decoded_data, 18, 16);
|
||||
uint8_t card_crc = bit_lib_get_bits_16(decoded_data, 34, 8);
|
||||
uint8_t calc_crc = protocol_paradox_calculate_checksum(fc, card_id);
|
||||
|
||||
furi_string_cat_printf(result, "Facility: %u\r\n", fc);
|
||||
furi_string_cat_printf(result, "Card: %u\r\n", card_id);
|
||||
furi_string_cat_printf(result, "Data: ");
|
||||
for(size_t i = 0; i < PARADOX_DECODED_DATA_SIZE; i++) {
|
||||
furi_string_cat_printf(result, "%02X", decoded_data[i]);
|
||||
}
|
||||
furi_string_cat_printf(result, "CRC: %u Calc CRC: %u\r\n", card_crc, calc_crc);
|
||||
if(card_crc != calc_crc) furi_string_cat_printf(result, "CRC Mismatch, Invalid Card!\r\n");
|
||||
};
|
||||
|
||||
void protocol_paradox_render_brief_data(ProtocolParadox* protocol, FuriString* result) {
|
||||
@@ -154,8 +182,15 @@ void protocol_paradox_render_brief_data(ProtocolParadox* protocol, FuriString* r
|
||||
|
||||
uint8_t fc = bit_lib_get_bits(decoded_data, 10, 8);
|
||||
uint16_t card_id = bit_lib_get_bits_16(decoded_data, 18, 16);
|
||||
uint8_t card_crc = bit_lib_get_bits_16(decoded_data, 34, 8);
|
||||
uint8_t calc_crc = protocol_paradox_calculate_checksum(fc, card_id);
|
||||
|
||||
furi_string_cat_printf(result, "FC: %03u, Card: %05u", fc, card_id);
|
||||
furi_string_cat_printf(result, "FC: %03u, Card: %05u\r\n", fc, card_id);
|
||||
if(calc_crc == card_crc) {
|
||||
furi_string_cat_printf(result, "CRC : %03u", card_crc);
|
||||
} else {
|
||||
furi_string_cat_printf(result, "Card is Invalid!");
|
||||
}
|
||||
};
|
||||
|
||||
bool protocol_paradox_write_data(ProtocolParadox* protocol, void* data) {
|
||||
|
||||
@@ -352,11 +352,27 @@ void nfc_generate_mf_classic(NfcDeviceData* data, uint8_t uid_len, MfClassicType
|
||||
}
|
||||
// Set SAK to 08
|
||||
data->nfc_data.sak = 0x08;
|
||||
} else if(type == MfClassicTypeMini) {
|
||||
// Set every block to 0xFF
|
||||
for(uint16_t i = 1; i < MF_MINI_TOTAL_SECTORS_NUM * 4; i += 1) {
|
||||
if(mf_classic_is_sector_trailer(i)) {
|
||||
nfc_generate_mf_classic_sector_trailer(mfc, i);
|
||||
} else {
|
||||
memset(&mfc->block[i].value, 0xFF, 16);
|
||||
}
|
||||
mf_classic_set_block_read(mfc, i, &mfc->block[i]);
|
||||
}
|
||||
// Set SAK to 09
|
||||
data->nfc_data.sak = 0x09;
|
||||
}
|
||||
|
||||
mfc->type = type;
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_mini(NfcDeviceData* data) {
|
||||
nfc_generate_mf_classic(data, 4, MfClassicTypeMini);
|
||||
}
|
||||
|
||||
static void nfc_generate_mf_classic_1k_4b_uid(NfcDeviceData* data) {
|
||||
nfc_generate_mf_classic(data, 4, MfClassicType1k);
|
||||
}
|
||||
@@ -438,6 +454,11 @@ static const NfcGenerator ntag_i2c_plus_2k_generator = {
|
||||
.generator_func = nfc_generate_ntag_i2c_plus_2k,
|
||||
};
|
||||
|
||||
static const NfcGenerator mifare_mini_generator = {
|
||||
.name = "Mifare Mini",
|
||||
.generator_func = nfc_generate_mf_mini,
|
||||
};
|
||||
|
||||
static const NfcGenerator mifare_classic_1k_4b_uid_generator = {
|
||||
.name = "Mifare Classic 1k 4byte UID",
|
||||
.generator_func = nfc_generate_mf_classic_1k_4b_uid,
|
||||
@@ -472,6 +493,7 @@ const NfcGenerator* const nfc_generators[] = {
|
||||
&ntag_i2c_2k_generator,
|
||||
&ntag_i2c_plus_1k_generator,
|
||||
&ntag_i2c_plus_2k_generator,
|
||||
&mifare_mini_generator,
|
||||
&mifare_classic_1k_4b_uid_generator,
|
||||
&mifare_classic_1k_7b_uid_generator,
|
||||
&mifare_classic_4k_4b_uid_generator,
|
||||
|
||||
@@ -163,6 +163,7 @@ void reader_analyzer_stop(ReaderAnalyzer* instance) {
|
||||
}
|
||||
if(instance->pcap) {
|
||||
nfc_debug_pcap_free(instance->pcap);
|
||||
instance->pcap = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -773,7 +773,10 @@ static bool nfc_device_save_mifare_classic_data(FlipperFormat* file, NfcDevice*
|
||||
do {
|
||||
if(!flipper_format_write_comment_cstr(file, "Mifare Classic specific data")) break;
|
||||
|
||||
if(data->type == MfClassicType1k) {
|
||||
if(data->type == MfClassicTypeMini) {
|
||||
if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "MINI")) break;
|
||||
blocks = 20;
|
||||
} else if(data->type == MfClassicType1k) {
|
||||
if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break;
|
||||
blocks = 64;
|
||||
} else if(data->type == MfClassicType4k) {
|
||||
@@ -871,7 +874,10 @@ static bool nfc_device_load_mifare_classic_data(FlipperFormat* file, NfcDevice*
|
||||
do {
|
||||
// Read Mifare Classic type
|
||||
if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break;
|
||||
if(!furi_string_cmp(temp_str, "1K")) {
|
||||
if(!furi_string_cmp(temp_str, "MINI")) {
|
||||
data->type = MfClassicTypeMini;
|
||||
data_blocks = 20;
|
||||
} else if(!furi_string_cmp(temp_str, "1K")) {
|
||||
data->type = MfClassicType1k;
|
||||
data_blocks = 64;
|
||||
} else if(!furi_string_cmp(temp_str, "4K")) {
|
||||
@@ -946,7 +952,9 @@ static bool nfc_device_save_mifare_classic_keys(NfcDevice* dev) {
|
||||
if(!flipper_format_file_open_always(file, furi_string_get_cstr(temp_str))) break;
|
||||
if(!flipper_format_write_header_cstr(file, nfc_keys_file_header, nfc_keys_file_version))
|
||||
break;
|
||||
if(data->type == MfClassicType1k) {
|
||||
if(data->type == MfClassicTypeMini) {
|
||||
if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "MINI")) break;
|
||||
} else if(data->type == MfClassicType1k) {
|
||||
if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "1K")) break;
|
||||
} else if(data->type == MfClassicType4k) {
|
||||
if(!flipper_format_write_string_cstr(file, "Mifare Classic type", "4K")) break;
|
||||
@@ -996,7 +1004,9 @@ bool nfc_device_load_key_cache(NfcDevice* dev) {
|
||||
if(furi_string_cmp_str(temp_str, nfc_keys_file_header)) break;
|
||||
if(version != nfc_keys_file_version) break;
|
||||
if(!flipper_format_read_string(file, "Mifare Classic type", temp_str)) break;
|
||||
if(!furi_string_cmp(temp_str, "1K")) {
|
||||
if(!furi_string_cmp(temp_str, "MINI")) {
|
||||
data->type = MfClassicTypeMini;
|
||||
} else if(!furi_string_cmp(temp_str, "1K")) {
|
||||
data->type = MfClassicType1k;
|
||||
} else if(!furi_string_cmp(temp_str, "4K")) {
|
||||
data->type = MfClassicType4k;
|
||||
|
||||
@@ -55,7 +55,9 @@ const char* nfc_mf_ul_type(MfUltralightType type, bool full_name) {
|
||||
}
|
||||
|
||||
const char* nfc_mf_classic_type(MfClassicType type) {
|
||||
if(type == MfClassicType1k) {
|
||||
if(type == MfClassicTypeMini) {
|
||||
return "Mifare Mini 0.3K";
|
||||
} else if(type == MfClassicType1k) {
|
||||
return "Mifare Classic 1K";
|
||||
} else if(type == MfClassicType4k) {
|
||||
return "Mifare Classic 4K";
|
||||
|
||||
@@ -142,21 +142,44 @@ static bool emv_decode_response(uint8_t* buff, uint16_t len, EmvApplication* app
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_AFL %x (len=%d)", tag, tlen);
|
||||
break;
|
||||
case EMV_TAG_CARD_NUM: // Track 2 Equivalent Data. 0xD0 delimits PAN from expiry (YYMM)
|
||||
case EMV_TAG_TRACK_1_EQUIV: {
|
||||
char track_1_equiv[80];
|
||||
memcpy(track_1_equiv, &buff[i], tlen);
|
||||
track_1_equiv[tlen] = '\0';
|
||||
success = true;
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_1_EQUIV %x : %s", tag, track_1_equiv);
|
||||
break;
|
||||
}
|
||||
case EMV_TAG_TRACK_2_EQUIV: {
|
||||
// 0xD0 delimits PAN from expiry (YYMM)
|
||||
for(int x = 1; x < tlen; x++) {
|
||||
if(buff[i + x + 1] > 0xD0) {
|
||||
memcpy(app->card_number, &buff[i], x + 1);
|
||||
app->card_number_len = x + 1;
|
||||
app->exp_year = (buff[i + x + 1] << 4) | (buff[i + x + 2] >> 4);
|
||||
app->exp_month = (buff[i + x + 2] << 4) | (buff[i + x + 3] >> 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Convert 4-bit to ASCII representation
|
||||
char track_2_equiv[41];
|
||||
uint8_t track_2_equiv_len = 0;
|
||||
for(int x = 0; x < tlen; x++) {
|
||||
char top = (buff[i + x] >> 4) + '0';
|
||||
char bottom = (buff[i + x] & 0x0F) + '0';
|
||||
track_2_equiv[x * 2] = top;
|
||||
track_2_equiv_len++;
|
||||
if(top == '?') break;
|
||||
track_2_equiv[x * 2 + 1] = bottom;
|
||||
track_2_equiv_len++;
|
||||
if(bottom == '?') break;
|
||||
}
|
||||
track_2_equiv[track_2_equiv_len] = '\0';
|
||||
success = true;
|
||||
FURI_LOG_T(
|
||||
TAG,
|
||||
"found EMV_TAG_CARD_NUM %x (len=%d)",
|
||||
EMV_TAG_CARD_NUM,
|
||||
app->card_number_len);
|
||||
FURI_LOG_T(TAG, "found EMV_TAG_TRACK_2_EQUIV %x : %s", tag, track_2_equiv);
|
||||
break;
|
||||
}
|
||||
case EMV_TAG_PAN:
|
||||
memcpy(app->card_number, &buff[i], tlen);
|
||||
app->card_number_len = tlen;
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
#define EMV_TAG_CARD_NAME 0x50
|
||||
#define EMV_TAG_FCI 0xBF0C
|
||||
#define EMV_TAG_LOG_CTRL 0x9F4D
|
||||
#define EMV_TAG_CARD_NUM 0x57
|
||||
#define EMV_TAG_TRACK_1_EQUIV 0x56
|
||||
#define EMV_TAG_TRACK_2_EQUIV 0x57
|
||||
#define EMV_TAG_PAN 0x5A
|
||||
#define EMV_TAG_AFL 0x94
|
||||
#define EMV_TAG_EXP_DATE 0x5F24
|
||||
|
||||
@@ -13,7 +13,9 @@
|
||||
#define MF_CLASSIC_WRITE_BLOCK_CMD (0xA0)
|
||||
|
||||
const char* mf_classic_get_type_str(MfClassicType type) {
|
||||
if(type == MfClassicType1k) {
|
||||
if(type == MfClassicTypeMini) {
|
||||
return "MIFARE Mini 0.3K";
|
||||
} else if(type == MfClassicType1k) {
|
||||
return "MIFARE Classic 1K";
|
||||
} else if(type == MfClassicType4k) {
|
||||
return "MIFARE Classic 4K";
|
||||
@@ -73,7 +75,9 @@ MfClassicSectorTrailer*
|
||||
}
|
||||
|
||||
uint8_t mf_classic_get_total_sectors_num(MfClassicType type) {
|
||||
if(type == MfClassicType1k) {
|
||||
if(type == MfClassicTypeMini) {
|
||||
return MF_MINI_TOTAL_SECTORS_NUM;
|
||||
} else if(type == MfClassicType1k) {
|
||||
return MF_CLASSIC_1K_TOTAL_SECTORS_NUM;
|
||||
} else if(type == MfClassicType4k) {
|
||||
return MF_CLASSIC_4K_TOTAL_SECTORS_NUM;
|
||||
@@ -83,7 +87,9 @@ uint8_t mf_classic_get_total_sectors_num(MfClassicType type) {
|
||||
}
|
||||
|
||||
uint16_t mf_classic_get_total_block_num(MfClassicType type) {
|
||||
if(type == MfClassicType1k) {
|
||||
if(type == MfClassicTypeMini) {
|
||||
return 20;
|
||||
} else if(type == MfClassicType1k) {
|
||||
return 64;
|
||||
} else if(type == MfClassicType4k) {
|
||||
return 256;
|
||||
@@ -361,10 +367,14 @@ bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
|
||||
}
|
||||
}
|
||||
|
||||
MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
|
||||
MfClassicType mf_classic_get_classic_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK) {
|
||||
UNUSED(ATQA1);
|
||||
if((ATQA0 == 0x44 || ATQA0 == 0x04) && (SAK == 0x08 || SAK == 0x88 || SAK == 0x09)) {
|
||||
return MfClassicType1k;
|
||||
if((ATQA0 == 0x44 || ATQA0 == 0x04)) {
|
||||
if((SAK == 0x08 || SAK == 0x88)) {
|
||||
return MfClassicType1k;
|
||||
} else if(SAK == 0x09) {
|
||||
return MfClassicTypeMini;
|
||||
}
|
||||
} else if((ATQA0 == 0x01) && (ATQA1 == 0x0F) && (SAK == 0x01)) {
|
||||
//skylanders support
|
||||
return MfClassicType1k;
|
||||
@@ -595,6 +605,14 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u
|
||||
if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
|
||||
mf_classic_set_block_read(data, i, &block_tmp);
|
||||
blocks_read++;
|
||||
} else if(i > start_block) {
|
||||
// Try to re-auth to read block in case prevous block was protected from read
|
||||
furi_hal_nfc_sleep();
|
||||
if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyA, &crypto, false, 0)) break;
|
||||
if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
|
||||
mf_classic_set_block_read(data, i, &block_tmp);
|
||||
blocks_read++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
blocks_read++;
|
||||
@@ -607,13 +625,20 @@ void mf_classic_read_sector(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data, u
|
||||
if(!key_b_found) break;
|
||||
FURI_LOG_D(TAG, "Try to read blocks with key B");
|
||||
key = nfc_util_bytes2num(sec_tr->key_b, sizeof(sec_tr->key_b));
|
||||
furi_hal_nfc_sleep();
|
||||
if(!mf_classic_auth(tx_rx, start_block, key, MfClassicKeyB, &crypto, false, 0)) break;
|
||||
for(size_t i = start_block; i < start_block + total_blocks; i++) {
|
||||
if(!mf_classic_is_block_read(data, i)) {
|
||||
if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
|
||||
mf_classic_set_block_read(data, i, &block_tmp);
|
||||
blocks_read++;
|
||||
} else if(i > start_block) {
|
||||
// Try to re-auth to read block in case prevous block was protected from read
|
||||
furi_hal_nfc_sleep();
|
||||
if(!mf_classic_auth(tx_rx, i, key, MfClassicKeyB, &crypto, false, 0)) break;
|
||||
if(mf_classic_read_block(tx_rx, &crypto, i, &block_tmp)) {
|
||||
mf_classic_set_block_read(data, i, &block_tmp);
|
||||
blocks_read++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
blocks_read++;
|
||||
@@ -665,6 +690,11 @@ static bool mf_classic_read_sector_with_reader(
|
||||
|
||||
// Read blocks
|
||||
for(uint8_t i = 0; i < sector->total_blocks; i++) {
|
||||
if(mf_classic_read_block(tx_rx, crypto, first_block + i, §or->block[i])) continue;
|
||||
if(i == 0) continue;
|
||||
// Try to auth to read next block in case previous is locked
|
||||
furi_hal_nfc_sleep();
|
||||
if(!mf_classic_auth(tx_rx, first_block + i, key, key_type, crypto, false, 0)) continue;
|
||||
mf_classic_read_block(tx_rx, crypto, first_block + i, §or->block[i]);
|
||||
}
|
||||
// Save sector keys in last block
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#define MF_CLASSIC_BLOCK_SIZE (16)
|
||||
#define MF_CLASSIC_TOTAL_BLOCKS_MAX (256)
|
||||
#define MF_MINI_TOTAL_SECTORS_NUM (5)
|
||||
#define MF_CLASSIC_1K_TOTAL_SECTORS_NUM (16)
|
||||
#define MF_CLASSIC_4K_TOTAL_SECTORS_NUM (40)
|
||||
|
||||
@@ -20,6 +21,7 @@
|
||||
typedef enum {
|
||||
MfClassicType1k,
|
||||
MfClassicType4k,
|
||||
MfClassicTypeMini,
|
||||
} MfClassicType;
|
||||
|
||||
typedef enum {
|
||||
@@ -94,7 +96,7 @@ const char* mf_classic_get_type_str(MfClassicType type);
|
||||
|
||||
bool mf_classic_check_card_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
|
||||
|
||||
MfClassicType mf_classic_get_classic_type(int8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
|
||||
MfClassicType mf_classic_get_classic_type(uint8_t ATQA0, uint8_t ATQA1, uint8_t SAK);
|
||||
|
||||
uint8_t mf_classic_get_total_sectors_num(MfClassicType type);
|
||||
|
||||
|
||||