Merge branch 'dev' into Barcode-Generator
@@ -0,0 +1,14 @@
|
||||
App(
|
||||
appid="am2320_temp_sensor",
|
||||
name="[AM2320] Temp. Sensor",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="am_temperature_sensor_app",
|
||||
cdefines=["APP_AM_TEMPERATURE_SENSOR"],
|
||||
requires=[
|
||||
"gui",
|
||||
],
|
||||
stack_size=2 * 1024,
|
||||
order=90,
|
||||
fap_icon="temperature_sensor.png",
|
||||
fap_category="GPIO",
|
||||
)
|
||||
@@ -0,0 +1,336 @@
|
||||
/* Flipper Plugin to read the values from a AM2320/AM2321 Sensor */
|
||||
/* Created by @xMasterX, original app (was used as template) by Mywk - https://github.com/Mywk */
|
||||
/* Lib used as reference: https://github.com/Gozem/am2320/blob/master/am2321.c*/
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
#include <furi_hal_i2c.h>
|
||||
#include <math.h>
|
||||
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define TS_DEFAULT_VALUE 0xFFFF
|
||||
|
||||
#define AM2320_ADDRESS (0x5C << 1)
|
||||
|
||||
#define DATA_BUFFER_SIZE 8
|
||||
|
||||
// External I2C BUS
|
||||
#define I2C_BUS &furi_hal_i2c_handle_external
|
||||
|
||||
typedef enum {
|
||||
TSSInitializing,
|
||||
TSSNoSensor,
|
||||
TSSPendingUpdate,
|
||||
} TSStatus;
|
||||
|
||||
typedef enum {
|
||||
TSEventTypeTick,
|
||||
TSEventTypeInput,
|
||||
} TSEventType;
|
||||
|
||||
typedef struct {
|
||||
TSEventType type;
|
||||
InputEvent input;
|
||||
} TSEvent;
|
||||
|
||||
extern const NotificationSequence sequence_blink_red_100;
|
||||
extern const NotificationSequence sequence_blink_blue_100;
|
||||
|
||||
static TSStatus temperature_sensor_current_status = TSSInitializing;
|
||||
|
||||
// Temperature and Humidity data buffers, ready to print
|
||||
char ts_data_buffer_temperature_c[DATA_BUFFER_SIZE];
|
||||
char ts_data_buffer_temperature_f[DATA_BUFFER_SIZE];
|
||||
char ts_data_buffer_relative_humidity[DATA_BUFFER_SIZE];
|
||||
char ts_data_buffer_absolute_humidity[DATA_BUFFER_SIZE];
|
||||
|
||||
// CRC16 calculation
|
||||
static uint16_t get_crc16(const uint8_t* buf, size_t len) {
|
||||
uint16_t crc = 0xFFFF;
|
||||
|
||||
while(len--) {
|
||||
crc ^= (uint16_t)*buf++;
|
||||
for(unsigned i = 0; i < 8; i++) {
|
||||
if(crc & 0x0001) {
|
||||
crc >>= 1;
|
||||
crc ^= 0xA001;
|
||||
} else {
|
||||
crc >>= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
// Combine bytes
|
||||
static uint16_t combine_bytes(uint8_t msb, uint8_t lsb) {
|
||||
return ((uint16_t)msb << 8) | (uint16_t)lsb;
|
||||
}
|
||||
|
||||
// Executes an I2C wake up, sends command and reads result
|
||||
// true if fetch was successful, false otherwise
|
||||
static bool temperature_sensor_get_data(uint8_t* buffer, uint8_t size) {
|
||||
uint32_t timeout = furi_ms_to_ticks(100);
|
||||
uint8_t cmdbuffer[3] = {0, 0, 0};
|
||||
bool ret = false;
|
||||
|
||||
// Aquire I2C bus
|
||||
furi_hal_i2c_acquire(I2C_BUS);
|
||||
|
||||
// Wake UP AM2320 (sensor goes to sleep to not warm up and affect the humidity sensor)
|
||||
furi_hal_i2c_is_device_ready(I2C_BUS, (uint8_t)AM2320_ADDRESS, timeout);
|
||||
// Check if device woken up then we do next stuff
|
||||
if(furi_hal_i2c_is_device_ready(I2C_BUS, (uint8_t)AM2320_ADDRESS, timeout)) {
|
||||
// Wait a bit
|
||||
furi_delay_us(1000);
|
||||
|
||||
// Prepare command: Addr 0x03, start register = 0x00, number of registers to read = 0x04
|
||||
cmdbuffer[0] = 0x03;
|
||||
cmdbuffer[1] = 0x00;
|
||||
cmdbuffer[2] = 0x04;
|
||||
|
||||
// Transmit command to read registers
|
||||
ret = furi_hal_i2c_tx(I2C_BUS, (uint8_t)AM2320_ADDRESS, cmdbuffer, 3, timeout);
|
||||
|
||||
// Wait a bit
|
||||
furi_delay_us(1600);
|
||||
if(ret) {
|
||||
/*
|
||||
* Read out 8 bytes of data
|
||||
* Byte 0: Should be Modbus function code 0x03
|
||||
* Byte 1: Should be number of registers to read (0x04)
|
||||
* Byte 2: Humidity msb
|
||||
* Byte 3: Humidity lsb
|
||||
* Byte 4: Temperature msb
|
||||
* Byte 5: Temperature lsb
|
||||
* Byte 6: CRC lsb byte
|
||||
* Byte 7: CRC msb byte
|
||||
*/
|
||||
ret = furi_hal_i2c_rx(I2C_BUS, (uint8_t)AM2320_ADDRESS, buffer, size, timeout);
|
||||
}
|
||||
}
|
||||
// Release i2c bus
|
||||
furi_hal_i2c_release(I2C_BUS);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Fetches temperature and humidity from sensor
|
||||
// Temperature and humidity must be preallocated
|
||||
// true if fetch was successful, false otherwise
|
||||
static bool temperature_sensor_fetch_info(double* temperature, double* humidity) {
|
||||
*humidity = (float)0;
|
||||
bool ret = false;
|
||||
|
||||
uint8_t buffer[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
// Fetch data from sensor
|
||||
ret = temperature_sensor_get_data(buffer, 8);
|
||||
|
||||
// If we got no result
|
||||
if(!ret) return false;
|
||||
|
||||
if(buffer[0] != 0x03) return false; // must be 0x03 modbus reply
|
||||
if(buffer[1] != 0x04) return false; // must be 0x04 number of registers reply
|
||||
|
||||
// Check CRC16 sum, if not correct - return false
|
||||
uint16_t crcdata = get_crc16(buffer, 6);
|
||||
uint16_t crcread = combine_bytes(buffer[7], buffer[6]);
|
||||
if(crcdata != crcread) return false;
|
||||
|
||||
// Combine bytes for temp and humidity
|
||||
uint16_t temp16 = combine_bytes(buffer[4], buffer[5]);
|
||||
uint16_t humi16 = combine_bytes(buffer[2], buffer[3]);
|
||||
|
||||
/* Temperature resolution is 16Bit,
|
||||
* temperature highest bit (Bit15) is equal to 1 indicates a
|
||||
* negative temperature, the temperature highest bit (Bit15)
|
||||
* is equal to 0 indicates a positive temperature;
|
||||
* temperature in addition to the most significant bit (Bit14 ~ Bit0)
|
||||
* indicates the temperature sensor string value.
|
||||
* Temperature sensor value is a string of 10 times the
|
||||
* actual temperature value.
|
||||
*/
|
||||
if(temp16 & 0x8000) {
|
||||
temp16 = -(temp16 & 0x7FFF);
|
||||
}
|
||||
|
||||
// Prepare output data
|
||||
*temperature = (float)temp16 / 10.0;
|
||||
*humidity = (float)humi16 / 10.0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Draw callback
|
||||
|
||||
static void temperature_sensor_draw_callback(Canvas* canvas, void* ctx) {
|
||||
UNUSED(ctx);
|
||||
|
||||
canvas_clear(canvas);
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
canvas_draw_str(canvas, 2, 10, "AM2320/AM2321 Sensor");
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
canvas_draw_str(canvas, 2, 62, "Press back to exit.");
|
||||
|
||||
switch(temperature_sensor_current_status) {
|
||||
case TSSInitializing:
|
||||
canvas_draw_str(canvas, 2, 30, "Initializing..");
|
||||
break;
|
||||
case TSSNoSensor:
|
||||
canvas_draw_str(canvas, 2, 30, "No sensor found!");
|
||||
break;
|
||||
case TSSPendingUpdate: {
|
||||
canvas_draw_str(canvas, 3, 24, "Temperature");
|
||||
canvas_draw_str(canvas, 68, 24, "Humidity");
|
||||
|
||||
// Draw vertical lines
|
||||
canvas_draw_line(canvas, 61, 16, 61, 50);
|
||||
canvas_draw_line(canvas, 62, 16, 62, 50);
|
||||
|
||||
// Draw horizontal line
|
||||
canvas_draw_line(canvas, 2, 27, 122, 27);
|
||||
|
||||
// Draw temperature and humidity values
|
||||
canvas_draw_str(canvas, 8, 38, ts_data_buffer_temperature_c);
|
||||
canvas_draw_str(canvas, 42, 38, "C");
|
||||
canvas_draw_str(canvas, 8, 48, ts_data_buffer_temperature_f);
|
||||
canvas_draw_str(canvas, 42, 48, "F");
|
||||
canvas_draw_str(canvas, 68, 38, ts_data_buffer_relative_humidity);
|
||||
canvas_draw_str(canvas, 100, 38, "%");
|
||||
canvas_draw_str(canvas, 68, 48, ts_data_buffer_absolute_humidity);
|
||||
canvas_draw_str(canvas, 100, 48, "g/m3");
|
||||
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Input callback
|
||||
|
||||
static void temperature_sensor_input_callback(InputEvent* input_event, void* ctx) {
|
||||
furi_assert(ctx);
|
||||
FuriMessageQueue* event_queue = ctx;
|
||||
|
||||
TSEvent event = {.type = TSEventTypeInput, .input = *input_event};
|
||||
furi_message_queue_put(event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
// Timer callback
|
||||
|
||||
static void temperature_sensor_timer_callback(FuriMessageQueue* event_queue) {
|
||||
furi_assert(event_queue);
|
||||
|
||||
TSEvent event = {.type = TSEventTypeTick};
|
||||
furi_message_queue_put(event_queue, &event, 0);
|
||||
}
|
||||
|
||||
// App entry point
|
||||
|
||||
int32_t am_temperature_sensor_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
furi_hal_power_suppress_charge_enter();
|
||||
// Declare our variables and assign variables a default value
|
||||
TSEvent tsEvent;
|
||||
bool sensorFound = false;
|
||||
double celsius, fahrenheit, rel_humidity, abs_humidity = TS_DEFAULT_VALUE;
|
||||
|
||||
// Used for absolute humidity calculation
|
||||
double vapour_pressure = 0;
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(TSEvent));
|
||||
|
||||
// Register callbacks
|
||||
ViewPort* view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(view_port, temperature_sensor_draw_callback, NULL);
|
||||
view_port_input_callback_set(view_port, temperature_sensor_input_callback, event_queue);
|
||||
|
||||
// Create timer and register its callback
|
||||
FuriTimer* timer =
|
||||
furi_timer_alloc(temperature_sensor_timer_callback, FuriTimerTypePeriodic, event_queue);
|
||||
furi_timer_start(timer, furi_kernel_get_tick_frequency());
|
||||
|
||||
// Register viewport
|
||||
Gui* gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
||||
|
||||
// Used to notify the user by blinking red (error) or blue (fetch successful)
|
||||
NotificationApp* notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
while(1) {
|
||||
furi_check(furi_message_queue_get(event_queue, &tsEvent, FuriWaitForever) == FuriStatusOk);
|
||||
|
||||
// Handle events
|
||||
if(tsEvent.type == TSEventTypeInput) {
|
||||
// Exit on back key
|
||||
if(tsEvent.input.key ==
|
||||
InputKeyBack) // We dont check for type here, we can check the type of keypress like: (event.input.type == InputTypeShort)
|
||||
break;
|
||||
|
||||
} else if(tsEvent.type == TSEventTypeTick) {
|
||||
// Update sensor data
|
||||
// Fetch data and set the sensor current status accordingly
|
||||
sensorFound = temperature_sensor_fetch_info(&celsius, &rel_humidity);
|
||||
temperature_sensor_current_status = (sensorFound ? TSSPendingUpdate : TSSNoSensor);
|
||||
|
||||
if(sensorFound) {
|
||||
// Blink blue
|
||||
notification_message(notifications, &sequence_blink_blue_100);
|
||||
|
||||
if(celsius != TS_DEFAULT_VALUE && rel_humidity != TS_DEFAULT_VALUE) {
|
||||
// Convert celsius to fahrenheit
|
||||
fahrenheit = (celsius * 9 / 5) + 32;
|
||||
|
||||
// Calculate absolute humidity - For more info refer to https://github.com/Mywk/FlipperTemperatureSensor/issues/1
|
||||
// Calculate saturation vapour pressure first
|
||||
vapour_pressure =
|
||||
(double)6.11 *
|
||||
pow(10, (double)(((double)7.5 * celsius) / ((double)237.3 + celsius)));
|
||||
// Then the vapour pressure in Pa
|
||||
vapour_pressure = vapour_pressure * rel_humidity;
|
||||
// Calculate absolute humidity
|
||||
abs_humidity =
|
||||
(double)2.16679 * (double)(vapour_pressure / ((double)273.15 + celsius));
|
||||
|
||||
// Fill our buffers here, not on the canvas draw callback
|
||||
snprintf(ts_data_buffer_temperature_c, DATA_BUFFER_SIZE, "%.2f", celsius);
|
||||
snprintf(ts_data_buffer_temperature_f, DATA_BUFFER_SIZE, "%.2f", fahrenheit);
|
||||
snprintf(
|
||||
ts_data_buffer_relative_humidity, DATA_BUFFER_SIZE, "%.2f", rel_humidity);
|
||||
snprintf(
|
||||
ts_data_buffer_absolute_humidity, DATA_BUFFER_SIZE, "%.2f", abs_humidity);
|
||||
}
|
||||
|
||||
} else {
|
||||
// Reset our variables to their default values
|
||||
celsius = fahrenheit = rel_humidity = abs_humidity = TS_DEFAULT_VALUE;
|
||||
|
||||
// Blink red
|
||||
notification_message(notifications, &sequence_blink_red_100);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t wait_ticks = furi_ms_to_ticks(!sensorFound ? 100 : 500);
|
||||
furi_delay_tick(wait_ticks);
|
||||
}
|
||||
|
||||
furi_hal_power_suppress_charge_exit();
|
||||
// Dobby is freee (free our variables, Flipper will crash if we don't do this!)
|
||||
furi_timer_free(timer);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
view_port_free(view_port);
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 181 B |
@@ -370,8 +370,6 @@ static void arkanoid_update_timer_callback(FuriMessageQueue* event_queue) {
|
||||
int32_t arkanoid_game_app(void* p) {
|
||||
UNUSED(p);
|
||||
int32_t return_code = 0;
|
||||
// Set random seed from interrupts
|
||||
srand(DWT->CYCCNT);
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
|
||||
|
||||
@@ -448,14 +446,13 @@ int32_t arkanoid_game_app(void* p) {
|
||||
arkanoid_state->ball_state.dy = -1;
|
||||
//start the game flag
|
||||
arkanoid_state->gameStarted = true;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Event timeout
|
||||
FURI_LOG_D(TAG, "furi_message_queue: Event timeout");
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
|
||||
@@ -339,8 +339,6 @@ int32_t barcode_generator_app(void* p) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D("barcode_generator", "osMessageQueue: event timeout");
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
#include "dap_config.h"
|
||||
#include "gui/dap_gui.h"
|
||||
#include "usb/dap_v2_usb.h"
|
||||
#include <dialogs/dialogs.h>
|
||||
#include "dap_link_icons.h"
|
||||
|
||||
/***************************************************************************/
|
||||
/****************************** DAP COMMON *********************************/
|
||||
@@ -495,6 +497,24 @@ DapConfig* dap_app_get_config(DapApp* app) {
|
||||
int32_t dap_link_app(void* p) {
|
||||
UNUSED(p);
|
||||
|
||||
if(furi_hal_usb_is_locked()) {
|
||||
DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
|
||||
DialogMessage* message = dialog_message_alloc();
|
||||
dialog_message_set_header(message, "Connection\nis active!", 3, 2, AlignLeft, AlignTop);
|
||||
dialog_message_set_text(
|
||||
message,
|
||||
"Disconnect from\nPC or phone to\nuse this function.",
|
||||
3,
|
||||
30,
|
||||
AlignLeft,
|
||||
AlignTop);
|
||||
dialog_message_set_icon(message, &I_ActiveConnection_50x64, 78, 0);
|
||||
dialog_message_show(dialogs, message);
|
||||
dialog_message_free(message);
|
||||
furi_record_close(RECORD_DIALOGS);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// alloc app
|
||||
DapApp* app = dap_app_alloc();
|
||||
app_handle = app;
|
||||
|
||||
@@ -72,8 +72,8 @@ void dap_scene_config_on_enter(void* context) {
|
||||
variable_item_set_current_value_index(item, config->uart_swap);
|
||||
variable_item_set_current_value_text(item, uart_swap[config->uart_swap]);
|
||||
|
||||
item = variable_item_list_add(var_item_list, "Help and Pinout", 0, NULL, NULL);
|
||||
item = variable_item_list_add(var_item_list, "About", 0, NULL, NULL);
|
||||
variable_item_list_add(var_item_list, "Help and Pinout", 0, NULL, NULL);
|
||||
variable_item_list_add(var_item_list, "About", 0, NULL, NULL);
|
||||
|
||||
variable_item_list_set_selected_item(
|
||||
var_item_list, scene_manager_get_scene_state(app->scene_manager, DapSceneConfig));
|
||||
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -8,7 +8,8 @@
|
||||
#define DHT_POLLING_CONTROL 1 //Включение проверки частоты опроса датчика
|
||||
#define DHT_POLLING_INTERVAL_DHT11 \
|
||||
2000 //Интервал опроса DHT11 (0.5 Гц по даташиту). Можно поставить 1500, будет работать
|
||||
#define DHT_POLLING_INTERVAL_DHT22 1000 //Интервал опроса DHT22 (1 Гц по даташиту)
|
||||
//Костыль, временно 2 секунды для датчика AM2302
|
||||
#define DHT_POLLING_INTERVAL_DHT22 2000 //Интервал опроса DHT22 (1 Гц по даташиту)
|
||||
#define DHT_IRQ_CONTROL //Выключать прерывания во время обмена данных с датчиком
|
||||
/* Структура возвращаемых датчиком данных */
|
||||
typedef struct {
|
||||
|
||||
@@ -11,18 +11,18 @@ const GpioPin RX_14 = {.pin = LL_GPIO_PIN_7, .port = GPIOB};
|
||||
|
||||
//Перечень достуных портов ввода/вывода
|
||||
static const GpioItem gpio_item[] = {
|
||||
{2, "2", &gpio_ext_pa7},
|
||||
{3, "3", &gpio_ext_pa6},
|
||||
{4, "4", &gpio_ext_pa4},
|
||||
{5, "5", &gpio_ext_pb3},
|
||||
{6, "6", &gpio_ext_pb2},
|
||||
{7, "7", &gpio_ext_pc3},
|
||||
{10, "10 (SWC)", &SWC_10},
|
||||
{2, "2 (A7)", &gpio_ext_pa7},
|
||||
{3, "3 (A6)", &gpio_ext_pa6},
|
||||
{4, "4 (A4)", &gpio_ext_pa4},
|
||||
{5, "5 (B3)", &gpio_ext_pb3},
|
||||
{6, "6 (B2)", &gpio_ext_pb2},
|
||||
{7, "7 (C3)", &gpio_ext_pc3},
|
||||
{10, " 10(SWC) ", &SWC_10},
|
||||
{12, "12 (SIO)", &SIO_12},
|
||||
{13, "13 (TX)", &TX_13},
|
||||
{14, "14 (RX)", &RX_14},
|
||||
{15, "15", &gpio_ext_pc1},
|
||||
{16, "16", &gpio_ext_pc0},
|
||||
{15, "15 (C1)", &gpio_ext_pc1},
|
||||
{16, "16 (C0)", &gpio_ext_pc0},
|
||||
{17, "17 (1W)", &ibutton_gpio}};
|
||||
|
||||
//Данные плагина
|
||||
@@ -442,6 +442,8 @@ int32_t quenon_dht_mon_app() {
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ void sensorEdit_scene(PluginData* app) {
|
||||
app->item =
|
||||
variable_item_list_add(variable_item_list, "Type:", 2, addSensor_sensorTypeChanged, app);
|
||||
|
||||
variable_item_set_current_value_index(nameItem, app->currentSensorEdit->type);
|
||||
variable_item_set_current_value_index(app->item, app->currentSensorEdit->type);
|
||||
variable_item_set_current_value_text(app->item, sensorsTypes[app->currentSensorEdit->type]);
|
||||
|
||||
//GPIO
|
||||
@@ -93,6 +93,9 @@ void sensorEdit_scene(PluginData* app) {
|
||||
app->item, DHTMon_GPIO_getName(app->currentSensorEdit->GPIO));
|
||||
variable_item_list_add(variable_item_list, "Save", 1, NULL, app);
|
||||
|
||||
//Сброс выбранного пункта в ноль
|
||||
variable_item_list_set_selected_item(variable_item_list, 0);
|
||||
|
||||
view_dispatcher_switch_to_view(app->view_dispatcher, ADDSENSOR_MENU_VIEW);
|
||||
}
|
||||
void sensorEdit_sceneRemove(void) {
|
||||
|
||||
@@ -9,4 +9,5 @@ App(
|
||||
order=100,
|
||||
fap_icon="flappy_10px.png",
|
||||
fap_category="Games",
|
||||
fap_icon_assets="assets",
|
||||
)
|
||||
|
||||
|
After Width: | Height: | Size: 113 B |
|
After Width: | Height: | Size: 116 B |
|
After Width: | Height: | Size: 116 B |
@@ -0,0 +1 @@
|
||||
3
|
||||
@@ -1,54 +0,0 @@
|
||||
#include <furi.h>
|
||||
|
||||
uint8_t bird_array[3][15][11] = {
|
||||
{
|
||||
{0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0},
|
||||
{0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0},
|
||||
{0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0},
|
||||
{0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0},
|
||||
{0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0},
|
||||
{0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1},
|
||||
{1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1},
|
||||
{1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1},
|
||||
{1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1},
|
||||
{1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0},
|
||||
{1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0},
|
||||
{0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0},
|
||||
{0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0},
|
||||
{0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0},
|
||||
{0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0},
|
||||
},
|
||||
{
|
||||
{0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0},
|
||||
{0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0},
|
||||
{0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0},
|
||||
{0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0},
|
||||
{0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0},
|
||||
{0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1},
|
||||
{1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1},
|
||||
{1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1},
|
||||
{1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1},
|
||||
{1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0},
|
||||
{1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0},
|
||||
{0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0},
|
||||
{0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0},
|
||||
{0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0},
|
||||
{0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0},
|
||||
},
|
||||
{
|
||||
{0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0},
|
||||
{0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0},
|
||||
{0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0},
|
||||
{0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0},
|
||||
{0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0},
|
||||
{0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1},
|
||||
{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1},
|
||||
{1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 1},
|
||||
{1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1},
|
||||
{1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0},
|
||||
{1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0},
|
||||
{0, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0},
|
||||
{0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0},
|
||||
{0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0},
|
||||
{0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0},
|
||||
}};
|
||||
@@ -1,9 +1,10 @@
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <input/input.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "bird.h"
|
||||
#include <FlappyBird_icons.h>
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/icon_animation_i.h>
|
||||
#include <input/input.h>
|
||||
|
||||
#define TAG "Flappy"
|
||||
#define DEBUG false
|
||||
@@ -36,6 +37,7 @@ typedef struct {
|
||||
typedef struct {
|
||||
float gravity;
|
||||
POINT point;
|
||||
IconAnimation* sprite;
|
||||
} BIRD;
|
||||
|
||||
typedef struct {
|
||||
@@ -89,6 +91,7 @@ static void flappy_game_state_init(GameState* const game_state) {
|
||||
bird.gravity = 0.0f;
|
||||
bird.point.x = 15;
|
||||
bird.point.y = 32;
|
||||
bird.sprite = icon_animation_alloc(&A_bird);
|
||||
|
||||
game_state->debug = DEBUG;
|
||||
game_state->bird = bird;
|
||||
@@ -100,6 +103,11 @@ static void flappy_game_state_init(GameState* const game_state) {
|
||||
flappy_game_random_pilar(game_state);
|
||||
}
|
||||
|
||||
static void flappy_game_state_free(GameState* const game_state) {
|
||||
icon_animation_free(game_state->bird.sprite);
|
||||
free(game_state);
|
||||
}
|
||||
|
||||
static void flappy_game_tick(GameState* const game_state) {
|
||||
if(game_state->state == GameStateLife) {
|
||||
if(!game_state->debug) {
|
||||
@@ -213,27 +221,16 @@ static void flappy_game_render_callback(Canvas* const canvas, void* ctx) {
|
||||
FLIPPER_LCD_HEIGHT - pilar->height - FLAPPY_GAB_HEIGHT);
|
||||
}
|
||||
}
|
||||
// Flappy
|
||||
for(int h = 0; h < FLAPPY_BIRD_HEIGHT; h++) {
|
||||
for(int w = 0; w < FLAPPY_BIRD_WIDTH; w++) {
|
||||
// Switch animation
|
||||
int bird = 0;
|
||||
if(game_state->bird.gravity < -0.5)
|
||||
bird = 1;
|
||||
else
|
||||
bird = 2;
|
||||
|
||||
// Draw bird pixels
|
||||
if(bird_array[bird][h][w] == 1) {
|
||||
int x = game_state->bird.point.x + h;
|
||||
int y = game_state->bird.point.y + w;
|
||||
// Switch animation
|
||||
game_state->bird.sprite->frame = 1;
|
||||
if(game_state->bird.gravity < -0.5)
|
||||
game_state->bird.sprite->frame = 0;
|
||||
else if(game_state->bird.gravity > 0.5)
|
||||
game_state->bird.sprite->frame = 2;
|
||||
|
||||
canvas_draw_dot(canvas, x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stats
|
||||
canvas_draw_icon_animation(
|
||||
canvas, game_state->bird.point.x, game_state->bird.point.y, game_state->bird.sprite);
|
||||
|
||||
canvas_set_font(canvas, FontSecondary);
|
||||
char buffer[12];
|
||||
@@ -345,14 +342,13 @@ int32_t flappy_game_app(void* p) {
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(event.type == EventTypeTick) {
|
||||
flappy_game_tick(game_state);
|
||||
}
|
||||
} else {
|
||||
//FURI_LOG_D(TAG, "osMessageQueue: event timeout");
|
||||
// event timeout
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
@@ -367,7 +363,7 @@ int32_t flappy_game_app(void* p) {
|
||||
delete_mutex(&state_mutex);
|
||||
|
||||
free_and_exit:
|
||||
free(game_state);
|
||||
flappy_game_state_free(game_state);
|
||||
furi_message_queue_free(event_queue);
|
||||
|
||||
return return_code;
|
||||
|
||||
@@ -134,6 +134,8 @@ void flipfrid_scene_entrypoint_on_event(FlipFridEvent event, FlipFridState* cont
|
||||
case InputKeyBack:
|
||||
context->is_running = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ void flipfrid_scene_load_custom_uids_on_event(FlipFridEvent event, FlipFridState
|
||||
case InputKeyBack:
|
||||
context->current_scene = SceneEntryPoint;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +158,8 @@ void flipfrid_scene_load_file_on_event(FlipFridEvent event, FlipFridState* conte
|
||||
case InputKeyBack:
|
||||
context->current_scene = SceneEntryPoint;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,6 +546,8 @@ void flipfrid_scene_run_attack_on_event(FlipFridEvent event, FlipFridState* cont
|
||||
notification_message(context->notify, &sequence_blink_stop);
|
||||
context->current_scene = SceneEntryPoint;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,8 @@ void flipfrid_scene_select_field_on_event(FlipFridEvent event, FlipFridState* co
|
||||
furi_string_reset(context->notification_msg);
|
||||
context->current_scene = SceneSelectFile;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
FURI_LOG_D(TAG, "Position: %d/%d", context->key_index, nb_bytes);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
# Game "15" for Flipper Zero
|
||||
|
||||
[Original link](https://github.com/x27/flipperzero-game15)
|
||||
|
||||
Logic game [Wikipedia](https://en.wikipedia.org/wiki/15_puzzle)
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
App(
|
||||
appid="game15",
|
||||
name="Game 15",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="game15_app",
|
||||
cdefines=["APP_GAME15"],
|
||||
requires=["gui"],
|
||||
stack_size=1 * 1024,
|
||||
fap_icon="game15_10px.png",
|
||||
order=30,
|
||||
fap_category="Games",
|
||||
)
|
||||
@@ -0,0 +1,468 @@
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <notification/notification.h>
|
||||
#include <notification/notification_messages.h>
|
||||
#include <storage/storage.h>
|
||||
|
||||
#include "sandbox.h"
|
||||
|
||||
#define FPS 20
|
||||
#define CELL_WIDTH 10
|
||||
#define CELL_HEIGHT 8
|
||||
#define MOVE_TICKS 5
|
||||
#define KEY_STACK_SIZE 16
|
||||
#define SAVING_DIRECTORY "/ext/apps/Games"
|
||||
#define SAVING_FILENAME SAVING_DIRECTORY "/game15.save"
|
||||
#define POPUP_MENU_ITEMS 2
|
||||
|
||||
typedef enum {
|
||||
DirectionNone,
|
||||
DirectionUp,
|
||||
DirectionDown,
|
||||
DirectionLeft,
|
||||
DirectionRight
|
||||
} direction_e;
|
||||
|
||||
typedef enum { ScenePlay, SceneWin, ScenePopup } scene_e;
|
||||
|
||||
typedef struct {
|
||||
uint8_t cell_index;
|
||||
uint8_t zero_index;
|
||||
uint8_t move_direction;
|
||||
uint8_t move_ticks;
|
||||
} moving_cell_t;
|
||||
|
||||
typedef struct {
|
||||
uint16_t top_record;
|
||||
scene_e scene;
|
||||
uint16_t move_count;
|
||||
uint32_t tick_count;
|
||||
uint8_t board[16];
|
||||
} game_state_t;
|
||||
|
||||
static game_state_t game_state;
|
||||
static NotificationApp* notification;
|
||||
static moving_cell_t moving_cell;
|
||||
static uint8_t loaded_saving_ticks;
|
||||
static uint8_t popup_menu_selected_item;
|
||||
|
||||
static const char* popup_menu_strings[] = {"Continue", "Reset"};
|
||||
|
||||
static uint8_t keys[KEY_STACK_SIZE];
|
||||
static uint8_t key_stack_head = 0;
|
||||
|
||||
static const uint8_t pic_cells[] = {
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0x30, 0xfc, 0x38, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0xfc, 0x30, 0xfc,
|
||||
0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x60, 0xfc, 0x30, 0xfc, 0x18, 0xfc, 0x0c, 0xfc, 0xfc, 0xfc,
|
||||
0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x60, 0xfc, 0xc0, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc,
|
||||
0x70, 0xfc, 0x78, 0xfc, 0x68, 0xfc, 0x6c, 0xfc, 0x6c, 0xfc, 0xec, 0xfc, 0xfc, 0xfc, 0x60, 0xfc,
|
||||
0xfc, 0xfc, 0x0c, 0xfc, 0x0c, 0xfc, 0x7c, 0xfc, 0xc0, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc,
|
||||
0x78, 0xfc, 0x0c, 0xfc, 0x0c, 0xfc, 0x7c, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc,
|
||||
0xfc, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc,
|
||||
0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0x78, 0xfc,
|
||||
0x78, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0xcc, 0xfc, 0xf8, 0xfc, 0xc0, 0xfc, 0xc0, 0xfc, 0x78, 0xfc,
|
||||
0xe6, 0xfd, 0x37, 0xff, 0x36, 0xff, 0x36, 0xff, 0x36, 0xff, 0x36, 0xff, 0x36, 0xff, 0xe6, 0xfd,
|
||||
0x8c, 0xfd, 0xce, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd, 0x8c, 0xfd,
|
||||
0xe6, 0xfd, 0x37, 0xff, 0x36, 0xff, 0x86, 0xfd, 0xc6, 0xfc, 0x66, 0xfc, 0x36, 0xfc, 0xf6, 0xff,
|
||||
0xe6, 0xfd, 0x37, 0xff, 0x36, 0xff, 0x86, 0xfd, 0x06, 0xff, 0x36, 0xff, 0x36, 0xff, 0xe6, 0xfd,
|
||||
0xc6, 0xfd, 0xe7, 0xfd, 0xa6, 0xfd, 0xb6, 0xfd, 0xb6, 0xfd, 0xb6, 0xff, 0xf6, 0xff, 0x86, 0xfd,
|
||||
0xf6, 0xff, 0x37, 0xfc, 0x36, 0xfc, 0xf6, 0xfd, 0x06, 0xff, 0x36, 0xff, 0x36, 0xff, 0xe6, 0xfd,
|
||||
};
|
||||
|
||||
static const uint8_t pic_digits[] = {
|
||||
0xf0, 0xf2, 0xf2, 0xf2, 0xf2, 0xf0, 0xf9, 0xf8, 0xf9, 0xf9, 0xf9, 0xf0, 0xf0, 0xf2, 0xf3,
|
||||
0xf1, 0xfc, 0xf0, 0xf0, 0xf3, 0xf1, 0xf3, 0xf2, 0xf0, 0xf3, 0xf1, 0xf2, 0xf2, 0xf0, 0xf3,
|
||||
0xf0, 0xfc, 0xf0, 0xf3, 0xf2, 0xf0, 0x00, 0x0c, 0x00, 0x02, 0x02, 0x00, 0x00, 0x03, 0x03,
|
||||
0x03, 0x03, 0x03, 0x00, 0x02, 0x00, 0x02, 0x02, 0x00, 0x00, 0x02, 0x00, 0x03, 0x03, 0x00,
|
||||
};
|
||||
|
||||
static const uint8_t pic_top[] = {11, 4, 0x88, 0xf8, 0xad, 0xfa, 0xad, 0xf8, 0x8d, 0xfe};
|
||||
static const uint8_t pic_move[] =
|
||||
{17, 4, 0x2e, 0x2a, 0xfe, 0xa4, 0xaa, 0xff, 0xaa, 0x2a, 0xff, 0x2e, 0x36, 0xfe};
|
||||
static const uint8_t pic_time[] = {15, 4, 0xa8, 0x8b, 0x2d, 0xe9, 0xad, 0xca, 0xad, 0x8b};
|
||||
|
||||
static const uint8_t pic_puzzled[] = {
|
||||
0xff, 0xcf, 0x00, 0xf3, 0xff, 0xfc, 0x3f, 0x03, 0xc0, 0xff, 0xf3, 0x0f, 0xdc, 0xff, 0xcf,
|
||||
0x00, 0xf3, 0xff, 0xfc, 0x3f, 0x03, 0xc0, 0xff, 0xf3, 0x0f, 0xdc, 0x03, 0xcc, 0x00, 0x03,
|
||||
0x38, 0x00, 0x0e, 0x03, 0xc0, 0x00, 0x30, 0x30, 0xdc, 0x03, 0xcc, 0x00, 0x03, 0x1c, 0x00,
|
||||
0x07, 0x03, 0xc0, 0x00, 0x30, 0x30, 0xdc, 0xff, 0xcf, 0x00, 0x03, 0x0e, 0x80, 0x03, 0x03,
|
||||
0xc0, 0xff, 0x33, 0xc0, 0xdc, 0xff, 0xcf, 0x00, 0x03, 0x07, 0xc0, 0x01, 0x03, 0xc0, 0xff,
|
||||
0x33, 0xc0, 0xdc, 0x03, 0xc0, 0x00, 0x83, 0x03, 0xe0, 0x00, 0x03, 0xc0, 0x00, 0x30, 0xc0,
|
||||
0xd0, 0x03, 0xc0, 0x00, 0xc3, 0x01, 0x70, 0x00, 0x03, 0xc0, 0x00, 0x30, 0xc0, 0xd0, 0x03,
|
||||
0xc0, 0xff, 0xf3, 0xff, 0xfc, 0x3f, 0xff, 0xcf, 0xff, 0xf3, 0xff, 0xdc, 0x03, 0xc0, 0xff,
|
||||
0xf3, 0xff, 0xfc, 0x3f, 0xff, 0xcf, 0xff, 0xf3, 0xff, 0xdc};
|
||||
|
||||
static void key_stack_init() {
|
||||
key_stack_head = 0;
|
||||
}
|
||||
|
||||
static uint8_t key_stack_pop() {
|
||||
return keys[--key_stack_head];
|
||||
}
|
||||
|
||||
static bool key_stack_is_empty() {
|
||||
return key_stack_head == 0;
|
||||
}
|
||||
|
||||
static int key_stack_push(uint8_t value) {
|
||||
if(key_stack_head != KEY_STACK_SIZE) {
|
||||
keys[key_stack_head] = value;
|
||||
key_stack_head++;
|
||||
return key_stack_head;
|
||||
} else
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool storage_game_state_load() {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
File* file = storage_file_alloc(storage);
|
||||
|
||||
uint16_t bytes_readed = 0;
|
||||
if(storage_file_open(file, SAVING_FILENAME, FSAM_READ, FSOM_OPEN_EXISTING))
|
||||
bytes_readed = storage_file_read(file, &game_state, sizeof(game_state_t));
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
return bytes_readed == sizeof(game_state_t);
|
||||
}
|
||||
|
||||
static void storage_game_state_save() {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
|
||||
if(storage_common_stat(storage, SAVING_DIRECTORY, NULL) == FSE_NOT_EXIST) {
|
||||
if(!storage_simply_mkdir(storage, SAVING_DIRECTORY)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
File* file = storage_file_alloc(storage);
|
||||
if(storage_file_open(file, SAVING_FILENAME, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
|
||||
storage_file_write(file, &game_state, sizeof(game_state_t));
|
||||
}
|
||||
storage_file_close(file);
|
||||
storage_file_free(file);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
}
|
||||
|
||||
static void set_moving_cell_by_direction(direction_e direction) {
|
||||
moving_cell.move_direction = DirectionNone;
|
||||
moving_cell.zero_index = 0xff;
|
||||
|
||||
for(int i = 0; i < 16; i++) {
|
||||
if(!game_state.board[i]) {
|
||||
moving_cell.zero_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(moving_cell.zero_index == 0xff) return;
|
||||
|
||||
uint8_t x = moving_cell.zero_index % 4;
|
||||
uint8_t y = moving_cell.zero_index / 4;
|
||||
|
||||
moving_cell.cell_index = moving_cell.zero_index;
|
||||
|
||||
if(direction == DirectionUp && y < 3)
|
||||
moving_cell.cell_index += 4;
|
||||
else if(direction == DirectionDown && y > 0)
|
||||
moving_cell.cell_index -= 4;
|
||||
else if(direction == DirectionLeft && x < 3)
|
||||
moving_cell.cell_index++;
|
||||
else if(direction == DirectionRight && x > 0)
|
||||
moving_cell.cell_index--;
|
||||
else
|
||||
return;
|
||||
|
||||
moving_cell.move_ticks = 0;
|
||||
moving_cell.move_direction = direction;
|
||||
}
|
||||
|
||||
static bool is_board_has_solution() {
|
||||
uint8_t i, j, inv = 0;
|
||||
for(i = 0; i < 16; ++i)
|
||||
if(game_state.board[i])
|
||||
for(j = 0; j < i; ++j)
|
||||
if(game_state.board[j] > game_state.board[i]) ++inv;
|
||||
for(i = 0; i < 16; ++i)
|
||||
if(game_state.board[i] == 0) inv += 1 + i / 4;
|
||||
|
||||
return inv % 2 == 0;
|
||||
}
|
||||
|
||||
static void board_init() {
|
||||
for(int i = 0; i < 16; i++) {
|
||||
game_state.board[i] = (i + 1) % 16;
|
||||
}
|
||||
|
||||
do {
|
||||
for(int i = 15; i >= 1; i--) {
|
||||
int j = rand() % (i + 1);
|
||||
uint8_t tmp = game_state.board[j];
|
||||
game_state.board[j] = game_state.board[i];
|
||||
game_state.board[i] = tmp;
|
||||
}
|
||||
} while(!is_board_has_solution());
|
||||
}
|
||||
|
||||
static void game_init() {
|
||||
game_state.scene = ScenePlay;
|
||||
game_state.move_count = 0;
|
||||
game_state.tick_count = 0;
|
||||
moving_cell.move_direction = DirectionNone;
|
||||
board_init();
|
||||
key_stack_init();
|
||||
popup_menu_selected_item = 0;
|
||||
}
|
||||
|
||||
static bool is_board_solved() {
|
||||
for(int i = 0; i < 16; i++)
|
||||
if(((i + 1) % 16) != game_state.board[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void game_tick() {
|
||||
switch(game_state.scene) {
|
||||
case ScenePlay:
|
||||
game_state.tick_count++;
|
||||
if(loaded_saving_ticks) loaded_saving_ticks--;
|
||||
if(moving_cell.move_direction == DirectionNone && !key_stack_is_empty()) {
|
||||
set_moving_cell_by_direction(key_stack_pop());
|
||||
if(moving_cell.move_direction == DirectionNone) {
|
||||
notification_message(notification, &sequence_single_vibro);
|
||||
key_stack_init();
|
||||
}
|
||||
}
|
||||
|
||||
if(moving_cell.move_direction != DirectionNone) {
|
||||
moving_cell.move_ticks++;
|
||||
if(moving_cell.move_ticks == MOVE_TICKS) {
|
||||
game_state.board[moving_cell.zero_index] =
|
||||
game_state.board[moving_cell.cell_index];
|
||||
game_state.board[moving_cell.cell_index] = 0;
|
||||
moving_cell.move_direction = DirectionNone;
|
||||
game_state.move_count++;
|
||||
}
|
||||
if(is_board_solved()) {
|
||||
notification_message(notification, &sequence_double_vibro);
|
||||
if(game_state.move_count < game_state.top_record || game_state.top_record == 0) {
|
||||
game_state.top_record = game_state.move_count;
|
||||
storage_game_state_save();
|
||||
}
|
||||
game_state.scene = SceneWin;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SceneWin:
|
||||
if(!key_stack_is_empty()) game_init();
|
||||
break;
|
||||
|
||||
case ScenePopup:
|
||||
if(!key_stack_is_empty()) {
|
||||
switch(key_stack_pop()) {
|
||||
case DirectionDown:
|
||||
popup_menu_selected_item++;
|
||||
popup_menu_selected_item = popup_menu_selected_item % POPUP_MENU_ITEMS;
|
||||
break;
|
||||
case DirectionUp:
|
||||
popup_menu_selected_item--;
|
||||
popup_menu_selected_item = popup_menu_selected_item % POPUP_MENU_ITEMS;
|
||||
break;
|
||||
case DirectionNone:
|
||||
if(popup_menu_selected_item == 0) {
|
||||
game_state.scene = ScenePlay;
|
||||
notification_message(notification, &sequence_single_vibro);
|
||||
} else if(popup_menu_selected_item == 1) {
|
||||
notification_message(notification, &sequence_single_vibro);
|
||||
game_init();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_cell(Canvas* canvas, uint8_t x, uint8_t y, uint8_t cell_number) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_rframe(canvas, x, y, 18, 14, 1);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_xbm(canvas, x + 4, y + 3, CELL_WIDTH, CELL_HEIGHT, pic_cells + cell_number * 16);
|
||||
}
|
||||
|
||||
static void board_draw(Canvas* canvas) {
|
||||
for(int i = 0; i < 16; i++) {
|
||||
if(game_state.board[i]) {
|
||||
if(moving_cell.move_direction == DirectionNone || moving_cell.cell_index != i)
|
||||
draw_cell(canvas, (i % 4) * 20 + 7, (i / 4) * 16 + 1, game_state.board[i]);
|
||||
if(moving_cell.move_direction != DirectionNone && moving_cell.cell_index == i) {
|
||||
uint8_t from_x = (moving_cell.cell_index % 4) * 20 + 7;
|
||||
uint8_t from_y = (moving_cell.cell_index / 4) * 16 + 1;
|
||||
uint8_t to_x = (moving_cell.zero_index % 4) * 20 + 7;
|
||||
uint8_t to_y = (moving_cell.zero_index / 4) * 16 + 1;
|
||||
int now_x = from_x + (to_x - from_x) * moving_cell.move_ticks / MOVE_TICKS;
|
||||
int now_y = from_y + (to_y - from_y) * moving_cell.move_ticks / MOVE_TICKS;
|
||||
draw_cell(canvas, now_x, now_y, game_state.board[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void number_draw(Canvas* canvas, uint8_t y, uint32_t value) {
|
||||
uint8_t x = 121;
|
||||
while(true) {
|
||||
uint8_t digit = value % 10;
|
||||
canvas_draw_xbm(canvas, x, y, 4, 6, pic_digits + digit * 6);
|
||||
x -= 5;
|
||||
value = value / 10;
|
||||
if(!value) break;
|
||||
}
|
||||
}
|
||||
|
||||
static void plate_draw(
|
||||
Canvas* canvas,
|
||||
uint8_t y,
|
||||
const uint8_t* header,
|
||||
uint32_t value,
|
||||
bool dont_draw_zero_value) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_rbox(canvas, 92, y, 35, 19, 2);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_xbm(canvas, 95, y + 3, header[0], header[1], &header[2]);
|
||||
if((!value && !dont_draw_zero_value) || value) number_draw(canvas, y + 10, value);
|
||||
}
|
||||
|
||||
static void info_draw(Canvas* canvas) {
|
||||
plate_draw(canvas, 1, pic_top, game_state.top_record, true);
|
||||
plate_draw(canvas, 22, pic_move, game_state.move_count, false);
|
||||
plate_draw(canvas, 43, pic_time, game_state.tick_count / FPS, false);
|
||||
}
|
||||
|
||||
static void gray_screen(Canvas* const canvas) {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
for(int x = 0; x < 128; x += 2) {
|
||||
for(int y = 0; y < 64; y++) {
|
||||
canvas_draw_dot(canvas, x + (y % 2 == 1 ? 0 : 1), y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void render_callback(Canvas* const canvas) {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, 0, 0, 128, 64);
|
||||
|
||||
if(game_state.scene == ScenePlay || game_state.scene == SceneWin ||
|
||||
game_state.scene == ScenePopup) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
board_draw(canvas);
|
||||
info_draw(canvas);
|
||||
|
||||
if(loaded_saving_ticks && game_state.scene != ScenePopup) {
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_rbox(canvas, 20, 24, 88, 16, 4);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_rframe(canvas, 20, 24, 88, 16, 4);
|
||||
canvas_draw_str_aligned(canvas, 64, 32, AlignCenter, AlignCenter, "Restore game ...");
|
||||
}
|
||||
}
|
||||
|
||||
if(game_state.scene == SceneWin) {
|
||||
gray_screen(canvas);
|
||||
canvas_draw_box(canvas, 7, 20, 114, 24);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(canvas, 8, 21, 112, 22);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_box(canvas, 10, 23, 108, 18);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_xbm(canvas, 14, 27, 100, 10, pic_puzzled);
|
||||
} else if(game_state.scene == ScenePopup) {
|
||||
gray_screen(canvas);
|
||||
canvas_set_color(canvas, ColorWhite);
|
||||
canvas_draw_rbox(canvas, 28, 16, 72, 32, 4);
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_rframe(canvas, 28, 16, 72, 32, 4);
|
||||
|
||||
for(int i = 0; i < POPUP_MENU_ITEMS; i++) {
|
||||
if(i == popup_menu_selected_item) {
|
||||
canvas_set_color(canvas, ColorBlack);
|
||||
canvas_draw_box(canvas, 34, 20 + 12 * i, 60, 12);
|
||||
}
|
||||
|
||||
canvas_set_color(canvas, i == popup_menu_selected_item ? ColorWhite : ColorBlack);
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 64, 26 + 12 * i, AlignCenter, AlignCenter, popup_menu_strings[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void game_event_handler(GameEvent const event) {
|
||||
if(event.type == EventTypeKey) {
|
||||
if(event.input.type == InputTypePress) {
|
||||
switch(event.input.key) {
|
||||
case InputKeyUp:
|
||||
key_stack_push(DirectionUp);
|
||||
break;
|
||||
case InputKeyDown:
|
||||
key_stack_push(DirectionDown);
|
||||
break;
|
||||
case InputKeyRight:
|
||||
key_stack_push(DirectionRight);
|
||||
break;
|
||||
case InputKeyLeft:
|
||||
key_stack_push(DirectionLeft);
|
||||
break;
|
||||
case InputKeyOk:
|
||||
if(game_state.scene == ScenePlay) {
|
||||
game_state.scene = ScenePopup;
|
||||
key_stack_init();
|
||||
} else
|
||||
key_stack_push(DirectionNone);
|
||||
break;
|
||||
case InputKeyBack:
|
||||
if(game_state.scene == ScenePopup) {
|
||||
game_state.scene = ScenePlay;
|
||||
} else {
|
||||
storage_game_state_save();
|
||||
sandbox_loop_exit();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(event.type == EventTypeTick) {
|
||||
game_tick();
|
||||
}
|
||||
}
|
||||
|
||||
static void game_alloc() {
|
||||
key_stack_init();
|
||||
notification = furi_record_open(RECORD_NOTIFICATION);
|
||||
notification_message_block(notification, &sequence_display_backlight_enforce_on);
|
||||
}
|
||||
|
||||
static void game_free() {
|
||||
notification_message_block(notification, &sequence_display_backlight_enforce_auto);
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
}
|
||||
|
||||
int32_t game15_app() {
|
||||
game_alloc();
|
||||
game_init();
|
||||
|
||||
loaded_saving_ticks = 0;
|
||||
if(storage_game_state_load()) {
|
||||
if(game_state.scene != ScenePlay)
|
||||
game_init();
|
||||
else
|
||||
loaded_saving_ticks = FPS;
|
||||
} else
|
||||
game_init();
|
||||
|
||||
sandbox_init(
|
||||
FPS, (SandboxRenderCallback)render_callback, (SandboxEventHandler)game_event_handler);
|
||||
sandbox_loop();
|
||||
sandbox_free();
|
||||
game_free();
|
||||
return 0;
|
||||
}
|
||||
|
After Width: | Height: | Size: 152 B |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,93 @@
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include "sandbox.h"
|
||||
|
||||
FuriMessageQueue* sandbox_event_queue;
|
||||
FuriMutex** sandbox_mutex;
|
||||
ViewPort* sandbox_view_port;
|
||||
Gui* sandbox_gui;
|
||||
FuriTimer* sandbox_timer;
|
||||
bool sandbox_loop_processing;
|
||||
SandboxRenderCallback sandbox_user_render_callback;
|
||||
SandboxEventHandler sandbox_user_event_handler;
|
||||
|
||||
static void sandbox_render_callback(Canvas* const canvas, void* context) {
|
||||
UNUSED(context);
|
||||
if(furi_mutex_acquire(sandbox_mutex, 25) != FuriStatusOk) return;
|
||||
|
||||
if(sandbox_user_render_callback) sandbox_user_render_callback(canvas);
|
||||
|
||||
furi_mutex_release(sandbox_mutex);
|
||||
}
|
||||
|
||||
static void sandbox_input_callback(InputEvent* input_event, void* context) {
|
||||
UNUSED(context);
|
||||
GameEvent event = {.type = EventTypeKey, .input = *input_event};
|
||||
furi_message_queue_put(sandbox_event_queue, &event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void sandbox_timer_callback(void* context) {
|
||||
UNUSED(context);
|
||||
GameEvent event = {.type = EventTypeTick};
|
||||
furi_message_queue_put(sandbox_event_queue, &event, 0);
|
||||
}
|
||||
|
||||
void sandbox_loop() {
|
||||
sandbox_loop_processing = true;
|
||||
while(sandbox_loop_processing) {
|
||||
GameEvent event;
|
||||
FuriStatus event_status = furi_message_queue_get(sandbox_event_queue, &event, 100);
|
||||
if(event_status != FuriStatusOk) {
|
||||
// timeout
|
||||
continue;
|
||||
}
|
||||
|
||||
furi_mutex_acquire(sandbox_mutex, FuriWaitForever);
|
||||
|
||||
if(sandbox_user_event_handler) sandbox_user_event_handler(event);
|
||||
|
||||
view_port_update(sandbox_view_port);
|
||||
furi_mutex_release(sandbox_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void sandbox_loop_exit() {
|
||||
sandbox_loop_processing = false;
|
||||
}
|
||||
|
||||
void sandbox_init(
|
||||
uint8_t fps,
|
||||
SandboxRenderCallback u_render_callback,
|
||||
SandboxEventHandler u_event_handler) {
|
||||
sandbox_user_render_callback = u_render_callback;
|
||||
sandbox_user_event_handler = u_event_handler;
|
||||
|
||||
sandbox_event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
|
||||
sandbox_mutex = furi_mutex_alloc(FuriMutexTypeNormal);
|
||||
|
||||
sandbox_view_port = view_port_alloc();
|
||||
view_port_draw_callback_set(sandbox_view_port, sandbox_render_callback, NULL);
|
||||
view_port_input_callback_set(sandbox_view_port, sandbox_input_callback, NULL);
|
||||
|
||||
sandbox_gui = furi_record_open(RECORD_GUI);
|
||||
gui_add_view_port(sandbox_gui, sandbox_view_port, GuiLayerFullscreen);
|
||||
|
||||
if(fps > 0) {
|
||||
sandbox_timer = furi_timer_alloc(sandbox_timer_callback, FuriTimerTypePeriodic, NULL);
|
||||
furi_timer_start(sandbox_timer, furi_kernel_get_tick_frequency() / fps);
|
||||
} else
|
||||
sandbox_timer = NULL;
|
||||
}
|
||||
|
||||
void sandbox_free() {
|
||||
if(sandbox_timer) furi_timer_free(sandbox_timer);
|
||||
|
||||
gui_remove_view_port(sandbox_gui, sandbox_view_port);
|
||||
view_port_enabled_set(sandbox_view_port, false);
|
||||
view_port_free(sandbox_view_port);
|
||||
|
||||
if(furi_mutex_acquire(sandbox_mutex, FuriWaitForever) == FuriStatusOk) {
|
||||
furi_mutex_free(sandbox_mutex);
|
||||
}
|
||||
furi_message_queue_free(sandbox_event_queue);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <input/input.h>
|
||||
|
||||
typedef enum {
|
||||
EventTypeTick,
|
||||
EventTypeKey,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
InputEvent input;
|
||||
} GameEvent;
|
||||
|
||||
typedef void (*SandboxRenderCallback)(Canvas* canvas);
|
||||
typedef void (*SandboxEventHandler)(GameEvent event);
|
||||
|
||||
void sandbox_init(
|
||||
uint8_t fps,
|
||||
SandboxRenderCallback render_callback,
|
||||
SandboxEventHandler event_handler);
|
||||
void sandbox_loop();
|
||||
void sandbox_loop_exit();
|
||||
void sandbox_free();
|
||||
@@ -1,6 +1,7 @@
|
||||
# GPS for Flipper Zero
|
||||
|
||||
[Original link](https://github.com/ezod/flipperzero-gps)
|
||||
|
||||
[Adafruit Ultimate GPS Breakout].
|
||||
|
||||

|
||||
|
||||
@@ -110,11 +110,11 @@ int32_t gps_app(void* p) {
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_D("GPS", "FuriMessageQueue: event timeout");
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
App(
|
||||
appid="hc_sr04",
|
||||
name="HC-SR04 Dist. Sensor",
|
||||
name="[HC-SR] Dist. Sensor",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="hc_sr04_app",
|
||||
cdefines=["APP_HC_SR04"],
|
||||
|
||||
@@ -230,6 +230,8 @@ int32_t hc_sr04_app() {
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,7 +500,6 @@ static void heap_defense_timer_callback(FuriMessageQueue* event_queue) {
|
||||
|
||||
int32_t heap_defence_app(void* p) {
|
||||
UNUSED(p);
|
||||
srand(DWT->CYCCNT);
|
||||
|
||||
//FURI_LOG_W(TAG, "Heap defence start %d", __LINE__);
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(GameEvent));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
App(
|
||||
appid="temperature_sensor",
|
||||
name="[HTU21D] Temp Sensor",
|
||||
name="[HTU21D] Temp. Sensor",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="temperature_sensor_app",
|
||||
cdefines=["APP_TEMPERATURE_SENSOR"],
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 274 KiB After Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 914 KiB After Width: | Height: | Size: 914 KiB |
|
After Width: | Height: | Size: 181 B |
@@ -318,6 +318,8 @@ int32_t metronome_app() {
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(event.input.type == InputTypeLong) {
|
||||
// hold events
|
||||
@@ -338,6 +340,8 @@ int32_t metronome_app() {
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(event.input.type == InputTypeRepeat) {
|
||||
// repeat events
|
||||
@@ -357,6 +361,8 @@ int32_t metronome_app() {
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,6 +476,8 @@ int32_t minesweeper_app(void* p) {
|
||||
// Exit the plugin
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(event.input.type == InputTypeLong) {
|
||||
// hold events
|
||||
@@ -493,6 +495,8 @@ int32_t minesweeper_app(void* p) {
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ typedef struct {
|
||||
MorseCodeWorker* worker;
|
||||
} MorseCode;
|
||||
|
||||
|
||||
static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
MorseCode* morse_code = ctx;
|
||||
furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
@@ -35,20 +34,26 @@ static void render_callback(Canvas* const canvas, void* ctx) {
|
||||
canvas_set_font(canvas, FontPrimary);
|
||||
|
||||
//write words
|
||||
elements_multiline_text_aligned(canvas, 64, 30, AlignCenter, AlignCenter, furi_string_get_cstr(morse_code->model->words));
|
||||
|
||||
// volume view_port
|
||||
elements_multiline_text_aligned(
|
||||
canvas, 64, 30, AlignCenter, AlignCenter, furi_string_get_cstr(morse_code->model->words));
|
||||
|
||||
// volume view_port
|
||||
uint8_t vol_bar_x_pos = 124;
|
||||
uint8_t vol_bar_y_pos = 0;
|
||||
const uint8_t volume_h =
|
||||
(64 / (COUNT_OF(MORSE_CODE_VOLUMES) - 1)) * morse_code->model->volume;
|
||||
const uint8_t volume_h = (64 / (COUNT_OF(MORSE_CODE_VOLUMES) - 1)) * morse_code->model->volume;
|
||||
canvas_draw_frame(canvas, vol_bar_x_pos, vol_bar_y_pos, 4, 64);
|
||||
canvas_draw_box(canvas, vol_bar_x_pos, vol_bar_y_pos + (64 - volume_h), 4, volume_h);
|
||||
|
||||
//dit bpm
|
||||
canvas_draw_str_aligned(
|
||||
canvas, 0, 10, AlignLeft, AlignCenter, furi_string_get_cstr(furi_string_alloc_printf("Dit: %ld ms", morse_code->model->dit_delta)));
|
||||
|
||||
canvas,
|
||||
0,
|
||||
10,
|
||||
AlignLeft,
|
||||
AlignCenter,
|
||||
furi_string_get_cstr(
|
||||
furi_string_alloc_printf("Dit: %ld ms", morse_code->model->dit_delta)));
|
||||
|
||||
//button info
|
||||
elements_button_center(canvas, "Press/Hold");
|
||||
furi_mutex_release(morse_code->model_mutex);
|
||||
@@ -59,9 +64,7 @@ static void input_callback(InputEvent* input_event, void* ctx) {
|
||||
furi_message_queue_put(morse_code->input_queue, input_event, FuriWaitForever);
|
||||
}
|
||||
|
||||
static void morse_code_worker_callback(
|
||||
FuriString* words,
|
||||
void* context) {
|
||||
static void morse_code_worker_callback(FuriString* words, void* context) {
|
||||
MorseCode* morse_code = context;
|
||||
furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
morse_code->model->words = words;
|
||||
@@ -115,45 +118,45 @@ int32_t morse_code_app() {
|
||||
InputEvent input;
|
||||
morse_code_worker_start(morse_code->worker);
|
||||
morse_code_worker_set_volume(
|
||||
morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]);
|
||||
morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta);
|
||||
while(furi_message_queue_get(morse_code->input_queue, &input, FuriWaitForever) == FuriStatusOk){
|
||||
morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]);
|
||||
morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta);
|
||||
while(furi_message_queue_get(morse_code->input_queue, &input, FuriWaitForever) ==
|
||||
FuriStatusOk) {
|
||||
furi_check(furi_mutex_acquire(morse_code->model_mutex, FuriWaitForever) == FuriStatusOk);
|
||||
if(input.key == InputKeyBack) {
|
||||
furi_mutex_release(morse_code->model_mutex);
|
||||
break;
|
||||
}else if(input.key == InputKeyOk){
|
||||
if(input.type == InputTypePress)
|
||||
morse_code_worker_play(morse_code->worker, true);
|
||||
else if(input.type == InputTypeRelease)
|
||||
morse_code_worker_play(morse_code->worker, false);
|
||||
}else if(input.key == InputKeyUp && input.type == InputTypePress){
|
||||
if(morse_code->model->volume < COUNT_OF(MORSE_CODE_VOLUMES) - 1)
|
||||
morse_code->model->volume++;
|
||||
morse_code_worker_set_volume(
|
||||
morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]);
|
||||
}else if(input.key == InputKeyDown && input.type == InputTypePress){
|
||||
if(morse_code->model->volume > 0)
|
||||
morse_code->model->volume--;
|
||||
morse_code_worker_set_volume(
|
||||
morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]);
|
||||
}else if(input.key == InputKeyLeft && input.type == InputTypePress){
|
||||
if(morse_code->model->dit_delta > 10)
|
||||
morse_code->model->dit_delta-=10;
|
||||
morse_code_worker_set_dit_delta(
|
||||
morse_code->worker, morse_code->model->dit_delta);
|
||||
}
|
||||
else if(input.key == InputKeyRight && input.type == InputTypePress){
|
||||
if(morse_code->model->dit_delta >= 10)
|
||||
morse_code->model->dit_delta+=10;
|
||||
morse_code_worker_set_dit_delta(
|
||||
morse_code->worker, morse_code->model->dit_delta);
|
||||
}
|
||||
|
||||
FURI_LOG_D("Input", "%s %s %ld", input_get_key_name(input.key), input_get_type_name(input.type), input.sequence);
|
||||
|
||||
furi_mutex_release(morse_code->model_mutex);
|
||||
break;
|
||||
} else if(input.key == InputKeyOk) {
|
||||
if(input.type == InputTypePress)
|
||||
morse_code_worker_play(morse_code->worker, true);
|
||||
else if(input.type == InputTypeRelease)
|
||||
morse_code_worker_play(morse_code->worker, false);
|
||||
} else if(input.key == InputKeyUp && input.type == InputTypePress) {
|
||||
if(morse_code->model->volume < COUNT_OF(MORSE_CODE_VOLUMES) - 1)
|
||||
morse_code->model->volume++;
|
||||
morse_code_worker_set_volume(
|
||||
morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]);
|
||||
} else if(input.key == InputKeyDown && input.type == InputTypePress) {
|
||||
if(morse_code->model->volume > 0) morse_code->model->volume--;
|
||||
morse_code_worker_set_volume(
|
||||
morse_code->worker, MORSE_CODE_VOLUMES[morse_code->model->volume]);
|
||||
} else if(input.key == InputKeyLeft && input.type == InputTypePress) {
|
||||
if(morse_code->model->dit_delta > 10) morse_code->model->dit_delta -= 10;
|
||||
morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta);
|
||||
} else if(input.key == InputKeyRight && input.type == InputTypePress) {
|
||||
if(morse_code->model->dit_delta >= 10) morse_code->model->dit_delta += 10;
|
||||
morse_code_worker_set_dit_delta(morse_code->worker, morse_code->model->dit_delta);
|
||||
}
|
||||
|
||||
FURI_LOG_D(
|
||||
"Input",
|
||||
"%s %s %ld",
|
||||
input_get_key_name(input.key),
|
||||
input_get_type_name(input.type),
|
||||
input.sequence);
|
||||
|
||||
furi_mutex_release(morse_code->model_mutex);
|
||||
view_port_update(morse_code->view_port);
|
||||
view_port_update(morse_code->view_port);
|
||||
}
|
||||
morse_code_worker_stop(morse_code->worker);
|
||||
morse_code_free(morse_code);
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
#include <furi_hal.h>
|
||||
#include <lib/flipper_format/flipper_format.h>
|
||||
|
||||
|
||||
#define TAG "MorseCodeWorker"
|
||||
|
||||
#define MORSE_CODE_VERSION 0
|
||||
|
||||
//A-Z0-1
|
||||
const char morse_array[36][6] ={
|
||||
".-", "-...", "-.-.", "-..", ".", "..-.", "--.", "....", "..", ".---", "-.-", ".-..", "--", "-.", "---", ".--.",
|
||||
"--.-", ".-.", "...", "-", "..-", "...-", ".--", "-..-", "-.--", "--..", ".----", "..---", "...--", "....-", ".....",
|
||||
"-....", "--...", "---..", "----.", "-----"
|
||||
};
|
||||
const char symbol_array[36] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
|
||||
'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};
|
||||
const char morse_array[36][6] = {".-", "-...", "-.-.", "-..", ".", "..-.",
|
||||
"--.", "....", "..", ".---", "-.-", ".-..",
|
||||
"--", "-.", "---", ".--.", "--.-", ".-.",
|
||||
"...", "-", "..-", "...-", ".--", "-..-",
|
||||
"-.--", "--..", ".----", "..---", "...--", "....-",
|
||||
".....", "-....", "--...", "---..", "----.", "-----"};
|
||||
const char symbol_array[36] = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
|
||||
'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0'};
|
||||
|
||||
struct MorseCodeWorker {
|
||||
FuriThread* thread;
|
||||
@@ -28,31 +29,28 @@ struct MorseCodeWorker {
|
||||
FuriString* words;
|
||||
};
|
||||
|
||||
void morse_code_worker_fill_buffer(MorseCodeWorker* instance, uint32_t duration){
|
||||
void morse_code_worker_fill_buffer(MorseCodeWorker* instance, uint32_t duration) {
|
||||
FURI_LOG_D("MorseCode: Duration", "%ld", duration);
|
||||
if( duration <= instance->dit_delta)
|
||||
if(duration <= instance->dit_delta)
|
||||
furi_string_push_back(instance->buffer, *DOT);
|
||||
else if(duration <= (instance->dit_delta * 3))
|
||||
furi_string_push_back(instance->buffer, *LINE);
|
||||
if(furi_string_size(instance->buffer) > 5)
|
||||
furi_string_reset(instance->buffer);
|
||||
if(furi_string_size(instance->buffer) > 5) furi_string_reset(instance->buffer);
|
||||
FURI_LOG_D("MorseCode: Buffer", "%s", furi_string_get_cstr(instance->buffer));
|
||||
}
|
||||
|
||||
void morse_code_worker_fill_letter(MorseCodeWorker* instance){
|
||||
if(furi_string_size(instance->words) > 63)
|
||||
furi_string_reset(instance->words);
|
||||
for (size_t i = 0; i < sizeof(morse_array); i++){
|
||||
if(furi_string_cmp_str(instance->buffer, morse_array[i]) == 0){
|
||||
furi_string_push_back(instance->words, symbol_array[i]);
|
||||
furi_string_reset(instance->buffer);
|
||||
break;
|
||||
}
|
||||
void morse_code_worker_fill_letter(MorseCodeWorker* instance) {
|
||||
if(furi_string_size(instance->words) > 63) furi_string_reset(instance->words);
|
||||
for(size_t i = 0; i < sizeof(morse_array); i++) {
|
||||
if(furi_string_cmp_str(instance->buffer, morse_array[i]) == 0) {
|
||||
furi_string_push_back(instance->words, symbol_array[i]);
|
||||
furi_string_reset(instance->buffer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
FURI_LOG_D("MorseCode: Words", "%s", furi_string_get_cstr(instance->words));
|
||||
}
|
||||
|
||||
|
||||
static int32_t morse_code_worker_thread_callback(void* context) {
|
||||
furi_assert(context);
|
||||
MorseCodeWorker* instance = context;
|
||||
@@ -61,16 +59,16 @@ static int32_t morse_code_worker_thread_callback(void* context) {
|
||||
uint32_t end_tick = 0;
|
||||
bool pushed = true;
|
||||
bool spaced = true;
|
||||
while(instance->is_running){
|
||||
while(instance->is_running) {
|
||||
furi_delay_ms(SLEEP);
|
||||
if(instance->play){
|
||||
if(!was_playing){
|
||||
if(instance->play) {
|
||||
if(!was_playing) {
|
||||
start_tick = furi_get_tick();
|
||||
furi_hal_speaker_start(FREQUENCY, instance->volume);
|
||||
was_playing = true;
|
||||
}
|
||||
}else{
|
||||
if(was_playing){
|
||||
} else {
|
||||
if(was_playing) {
|
||||
pushed = false;
|
||||
spaced = false;
|
||||
furi_hal_speaker_stop();
|
||||
@@ -80,21 +78,21 @@ static int32_t morse_code_worker_thread_callback(void* context) {
|
||||
start_tick = 0;
|
||||
}
|
||||
}
|
||||
if(!pushed){
|
||||
if(end_tick + (instance->dit_delta * 3) < furi_get_tick()){
|
||||
if(!pushed) {
|
||||
if(end_tick + (instance->dit_delta * 3) < furi_get_tick()) {
|
||||
//NEW LETTER
|
||||
morse_code_worker_fill_letter(instance);
|
||||
if(instance->callback)
|
||||
instance->callback(instance->words, instance->callback_context);
|
||||
instance->callback(instance->words, instance->callback_context);
|
||||
pushed = true;
|
||||
}
|
||||
}
|
||||
if(!spaced){
|
||||
if(end_tick + (instance->dit_delta * 7) < furi_get_tick()){
|
||||
if(!spaced) {
|
||||
if(end_tick + (instance->dit_delta * 7) < furi_get_tick()) {
|
||||
//NEW WORD
|
||||
furi_string_push_back(instance->words, *SPACE);
|
||||
if(instance->callback)
|
||||
instance->callback(instance->words, instance->callback_context);
|
||||
instance->callback(instance->words, instance->callback_context);
|
||||
spaced = true;
|
||||
}
|
||||
}
|
||||
@@ -132,17 +130,17 @@ void morse_code_worker_set_callback(
|
||||
instance->callback_context = context;
|
||||
}
|
||||
|
||||
void morse_code_worker_play(MorseCodeWorker* instance, bool play){
|
||||
void morse_code_worker_play(MorseCodeWorker* instance, bool play) {
|
||||
furi_assert(instance);
|
||||
instance->play = play;
|
||||
}
|
||||
|
||||
void morse_code_worker_set_volume(MorseCodeWorker* instance, float level){
|
||||
void morse_code_worker_set_volume(MorseCodeWorker* instance, float level) {
|
||||
furi_assert(instance);
|
||||
instance->volume = level;
|
||||
}
|
||||
|
||||
void morse_code_worker_set_dit_delta(MorseCodeWorker* instance, uint32_t delta){
|
||||
void morse_code_worker_set_dit_delta(MorseCodeWorker* instance, uint32_t delta) {
|
||||
furi_assert(instance);
|
||||
instance->dit_delta = delta;
|
||||
}
|
||||
|
||||
@@ -10,9 +10,7 @@
|
||||
#define LINE "-"
|
||||
#define SPACE " "
|
||||
|
||||
typedef void (*MorseCodeWorkerCallback)(
|
||||
FuriString* buffer,
|
||||
void* context);
|
||||
typedef void (*MorseCodeWorkerCallback)(FuriString* buffer, void* context);
|
||||
|
||||
typedef struct MorseCodeWorker MorseCodeWorker;
|
||||
|
||||
@@ -34,9 +32,3 @@ void morse_code_worker_play(MorseCodeWorker* instance, bool play);
|
||||
void morse_code_worker_set_volume(MorseCodeWorker* instance, float level);
|
||||
|
||||
void morse_code_worker_set_dit_delta(MorseCodeWorker* instance, uint32_t delta);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -372,6 +372,8 @@ int32_t mousejacker_app(void* p) {
|
||||
plugin_state->close_thread_please = false;
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -382,7 +384,7 @@ int32_t mousejacker_app(void* p) {
|
||||
}
|
||||
|
||||
furi_thread_free(plugin_state->mjthread);
|
||||
furi_hal_spi_release(nrf24_HANDLE);
|
||||
nrf24_deinit();
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
App(
|
||||
appid="nfc_magic",
|
||||
name="NFC Magic",
|
||||
apptype=FlipperAppType.EXTERNAL,
|
||||
entry_point="nfc_magic_app",
|
||||
requires=[
|
||||
"storage",
|
||||
"gui",
|
||||
],
|
||||
stack_size=4 * 1024,
|
||||
order=30,
|
||||
fap_icon="../../../assets/icons/Archive/Nfc_10px.png",
|
||||
fap_category="Tools",
|
||||
fap_private_libs=[
|
||||
Lib(
|
||||
name="magic",
|
||||
),
|
||||
],
|
||||
fap_icon_assets="assets",
|
||||
)
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,214 @@
|
||||
#include "magic.h"
|
||||
|
||||
#include <furi_hal_nfc.h>
|
||||
|
||||
#define TAG "Magic"
|
||||
|
||||
#define MAGIC_CMD_WUPA (0x40)
|
||||
#define MAGIC_CMD_WIPE (0x41)
|
||||
#define MAGIC_CMD_READ (0x43)
|
||||
#define MAGIC_CMD_WRITE (0x43)
|
||||
|
||||
#define MAGIC_MIFARE_READ_CMD (0x30)
|
||||
#define MAGIC_MIFARE_WRITE_CMD (0xA0)
|
||||
|
||||
#define MAGIC_ACK (0x0A)
|
||||
|
||||
#define MAGIC_BUFFER_SIZE (32)
|
||||
|
||||
bool magic_wupa() {
|
||||
bool magic_activated = false;
|
||||
uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
|
||||
uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
|
||||
uint16_t rx_len = 0;
|
||||
FuriHalNfcReturn ret = 0;
|
||||
|
||||
do {
|
||||
// Setup nfc poller
|
||||
furi_hal_nfc_exit_sleep();
|
||||
furi_hal_nfc_ll_txrx_on();
|
||||
furi_hal_nfc_ll_poll();
|
||||
ret = furi_hal_nfc_ll_set_mode(
|
||||
FuriHalNfcModePollNfca, FuriHalNfcBitrate106, FuriHalNfcBitrate106);
|
||||
if(ret != FuriHalNfcReturnOk) break;
|
||||
|
||||
furi_hal_nfc_ll_set_fdt_listen(FURI_HAL_NFC_LL_FDT_LISTEN_NFCA_POLLER);
|
||||
furi_hal_nfc_ll_set_fdt_poll(FURI_HAL_NFC_LL_FDT_POLL_NFCA_POLLER);
|
||||
furi_hal_nfc_ll_set_error_handling(FuriHalNfcErrorHandlingNfc);
|
||||
furi_hal_nfc_ll_set_guard_time(FURI_HAL_NFC_LL_GT_NFCA);
|
||||
|
||||
// Start communication
|
||||
tx_data[0] = MAGIC_CMD_WUPA;
|
||||
ret = furi_hal_nfc_ll_txrx_bits(
|
||||
tx_data,
|
||||
7,
|
||||
rx_data,
|
||||
sizeof(rx_data),
|
||||
&rx_len,
|
||||
FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON |
|
||||
FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP,
|
||||
furi_hal_nfc_ll_ms2fc(20));
|
||||
if(ret != FuriHalNfcReturnIncompleteByte) break;
|
||||
if(rx_len != 4) break;
|
||||
if(rx_data[0] != MAGIC_ACK) break;
|
||||
magic_activated = true;
|
||||
} while(false);
|
||||
|
||||
if(!magic_activated) {
|
||||
furi_hal_nfc_ll_txrx_off();
|
||||
furi_hal_nfc_start_sleep();
|
||||
}
|
||||
|
||||
return magic_activated;
|
||||
}
|
||||
|
||||
bool magic_data_access_cmd() {
|
||||
bool write_cmd_success = false;
|
||||
uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
|
||||
uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
|
||||
uint16_t rx_len = 0;
|
||||
FuriHalNfcReturn ret = 0;
|
||||
|
||||
do {
|
||||
tx_data[0] = MAGIC_CMD_WRITE;
|
||||
ret = furi_hal_nfc_ll_txrx_bits(
|
||||
tx_data,
|
||||
8,
|
||||
rx_data,
|
||||
sizeof(rx_data),
|
||||
&rx_len,
|
||||
FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON |
|
||||
FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP,
|
||||
furi_hal_nfc_ll_ms2fc(20));
|
||||
if(ret != FuriHalNfcReturnIncompleteByte) break;
|
||||
if(rx_len != 4) break;
|
||||
if(rx_data[0] != MAGIC_ACK) break;
|
||||
|
||||
write_cmd_success = true;
|
||||
} while(false);
|
||||
|
||||
if(!write_cmd_success) {
|
||||
furi_hal_nfc_ll_txrx_off();
|
||||
furi_hal_nfc_start_sleep();
|
||||
}
|
||||
|
||||
return write_cmd_success;
|
||||
}
|
||||
|
||||
bool magic_read_block(uint8_t block_num, MfClassicBlock* data) {
|
||||
furi_assert(data);
|
||||
|
||||
bool read_success = false;
|
||||
|
||||
uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
|
||||
uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
|
||||
uint16_t rx_len = 0;
|
||||
FuriHalNfcReturn ret = 0;
|
||||
|
||||
do {
|
||||
tx_data[0] = MAGIC_MIFARE_READ_CMD;
|
||||
tx_data[1] = block_num;
|
||||
ret = furi_hal_nfc_ll_txrx_bits(
|
||||
tx_data,
|
||||
2 * 8,
|
||||
rx_data,
|
||||
sizeof(rx_data),
|
||||
&rx_len,
|
||||
FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON,
|
||||
furi_hal_nfc_ll_ms2fc(20));
|
||||
|
||||
if(ret != FuriHalNfcReturnOk) break;
|
||||
if(rx_len != 16 * 8) break;
|
||||
memcpy(data->value, rx_data, sizeof(data->value));
|
||||
read_success = true;
|
||||
} while(false);
|
||||
|
||||
if(!read_success) {
|
||||
furi_hal_nfc_ll_txrx_off();
|
||||
furi_hal_nfc_start_sleep();
|
||||
}
|
||||
|
||||
return read_success;
|
||||
}
|
||||
|
||||
bool magic_write_blk(uint8_t block_num, MfClassicBlock* data) {
|
||||
furi_assert(data);
|
||||
|
||||
bool write_success = false;
|
||||
uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
|
||||
uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
|
||||
uint16_t rx_len = 0;
|
||||
FuriHalNfcReturn ret = 0;
|
||||
|
||||
do {
|
||||
tx_data[0] = MAGIC_MIFARE_WRITE_CMD;
|
||||
tx_data[1] = block_num;
|
||||
ret = furi_hal_nfc_ll_txrx_bits(
|
||||
tx_data,
|
||||
2 * 8,
|
||||
rx_data,
|
||||
sizeof(rx_data),
|
||||
&rx_len,
|
||||
FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP,
|
||||
furi_hal_nfc_ll_ms2fc(20));
|
||||
if(ret != FuriHalNfcReturnIncompleteByte) break;
|
||||
if(rx_len != 4) break;
|
||||
if(rx_data[0] != MAGIC_ACK) break;
|
||||
|
||||
memcpy(tx_data, data->value, sizeof(data->value));
|
||||
ret = furi_hal_nfc_ll_txrx_bits(
|
||||
tx_data,
|
||||
16 * 8,
|
||||
rx_data,
|
||||
sizeof(rx_data),
|
||||
&rx_len,
|
||||
FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP,
|
||||
furi_hal_nfc_ll_ms2fc(20));
|
||||
if(ret != FuriHalNfcReturnIncompleteByte) break;
|
||||
if(rx_len != 4) break;
|
||||
if(rx_data[0] != MAGIC_ACK) break;
|
||||
|
||||
write_success = true;
|
||||
} while(false);
|
||||
|
||||
if(!write_success) {
|
||||
furi_hal_nfc_ll_txrx_off();
|
||||
furi_hal_nfc_start_sleep();
|
||||
}
|
||||
|
||||
return write_success;
|
||||
}
|
||||
|
||||
bool magic_wipe() {
|
||||
bool wipe_success = false;
|
||||
uint8_t tx_data[MAGIC_BUFFER_SIZE] = {};
|
||||
uint8_t rx_data[MAGIC_BUFFER_SIZE] = {};
|
||||
uint16_t rx_len = 0;
|
||||
FuriHalNfcReturn ret = 0;
|
||||
|
||||
do {
|
||||
tx_data[0] = MAGIC_CMD_WIPE;
|
||||
ret = furi_hal_nfc_ll_txrx_bits(
|
||||
tx_data,
|
||||
8,
|
||||
rx_data,
|
||||
sizeof(rx_data),
|
||||
&rx_len,
|
||||
FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON |
|
||||
FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_RX_KEEP,
|
||||
furi_hal_nfc_ll_ms2fc(2000));
|
||||
|
||||
if(ret != FuriHalNfcReturnIncompleteByte) break;
|
||||
if(rx_len != 4) break;
|
||||
if(rx_data[0] != MAGIC_ACK) break;
|
||||
|
||||
wipe_success = true;
|
||||
} while(false);
|
||||
|
||||
return wipe_success;
|
||||
}
|
||||
|
||||
void magic_deactivate() {
|
||||
furi_hal_nfc_ll_txrx_off();
|
||||
furi_hal_nfc_start_sleep();
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/nfc/protocols/mifare_classic.h>
|
||||
|
||||
bool magic_wupa();
|
||||
|
||||
bool magic_read_block(uint8_t block_num, MfClassicBlock* data);
|
||||
|
||||
bool magic_data_access_cmd();
|
||||
|
||||
bool magic_write_blk(uint8_t block_num, MfClassicBlock* data);
|
||||
|
||||
bool magic_wipe();
|
||||
|
||||
void magic_deactivate();
|
||||
@@ -0,0 +1,169 @@
|
||||
#include "nfc_magic_i.h"
|
||||
|
||||
bool nfc_magic_custom_event_callback(void* context, uint32_t event) {
|
||||
furi_assert(context);
|
||||
NfcMagic* nfc_magic = context;
|
||||
return scene_manager_handle_custom_event(nfc_magic->scene_manager, event);
|
||||
}
|
||||
|
||||
bool nfc_magic_back_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
NfcMagic* nfc_magic = context;
|
||||
return scene_manager_handle_back_event(nfc_magic->scene_manager);
|
||||
}
|
||||
|
||||
void nfc_magic_tick_event_callback(void* context) {
|
||||
furi_assert(context);
|
||||
NfcMagic* nfc_magic = context;
|
||||
scene_manager_handle_tick_event(nfc_magic->scene_manager);
|
||||
}
|
||||
|
||||
void nfc_magic_show_loading_popup(void* context, bool show) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
|
||||
|
||||
if(show) {
|
||||
// Raise timer priority so that animations can play
|
||||
vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1);
|
||||
view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewLoading);
|
||||
} else {
|
||||
// Restore default timer priority
|
||||
vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY);
|
||||
}
|
||||
}
|
||||
|
||||
NfcMagic* nfc_magic_alloc() {
|
||||
NfcMagic* nfc_magic = malloc(sizeof(NfcMagic));
|
||||
|
||||
nfc_magic->worker = nfc_magic_worker_alloc();
|
||||
nfc_magic->view_dispatcher = view_dispatcher_alloc();
|
||||
nfc_magic->scene_manager = scene_manager_alloc(&nfc_magic_scene_handlers, nfc_magic);
|
||||
view_dispatcher_enable_queue(nfc_magic->view_dispatcher);
|
||||
view_dispatcher_set_event_callback_context(nfc_magic->view_dispatcher, nfc_magic);
|
||||
view_dispatcher_set_custom_event_callback(
|
||||
nfc_magic->view_dispatcher, nfc_magic_custom_event_callback);
|
||||
view_dispatcher_set_navigation_event_callback(
|
||||
nfc_magic->view_dispatcher, nfc_magic_back_event_callback);
|
||||
view_dispatcher_set_tick_event_callback(
|
||||
nfc_magic->view_dispatcher, nfc_magic_tick_event_callback, 100);
|
||||
|
||||
// Nfc device
|
||||
nfc_magic->nfc_dev = nfc_device_alloc();
|
||||
|
||||
// Open GUI record
|
||||
nfc_magic->gui = furi_record_open(RECORD_GUI);
|
||||
view_dispatcher_attach_to_gui(
|
||||
nfc_magic->view_dispatcher, nfc_magic->gui, ViewDispatcherTypeFullscreen);
|
||||
|
||||
// Open Notification record
|
||||
nfc_magic->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||
|
||||
// Submenu
|
||||
nfc_magic->submenu = submenu_alloc();
|
||||
view_dispatcher_add_view(
|
||||
nfc_magic->view_dispatcher, NfcMagicViewMenu, submenu_get_view(nfc_magic->submenu));
|
||||
|
||||
// Popup
|
||||
nfc_magic->popup = popup_alloc();
|
||||
view_dispatcher_add_view(
|
||||
nfc_magic->view_dispatcher, NfcMagicViewPopup, popup_get_view(nfc_magic->popup));
|
||||
|
||||
// Loading
|
||||
nfc_magic->loading = loading_alloc();
|
||||
view_dispatcher_add_view(
|
||||
nfc_magic->view_dispatcher, NfcMagicViewLoading, loading_get_view(nfc_magic->loading));
|
||||
|
||||
// Text Input
|
||||
nfc_magic->text_input = text_input_alloc();
|
||||
view_dispatcher_add_view(
|
||||
nfc_magic->view_dispatcher,
|
||||
NfcMagicViewTextInput,
|
||||
text_input_get_view(nfc_magic->text_input));
|
||||
|
||||
// Custom Widget
|
||||
nfc_magic->widget = widget_alloc();
|
||||
view_dispatcher_add_view(
|
||||
nfc_magic->view_dispatcher, NfcMagicViewWidget, widget_get_view(nfc_magic->widget));
|
||||
|
||||
return nfc_magic;
|
||||
}
|
||||
|
||||
void nfc_magic_free(NfcMagic* nfc_magic) {
|
||||
furi_assert(nfc_magic);
|
||||
|
||||
// Nfc device
|
||||
nfc_device_free(nfc_magic->nfc_dev);
|
||||
|
||||
// Submenu
|
||||
view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewMenu);
|
||||
submenu_free(nfc_magic->submenu);
|
||||
|
||||
// Popup
|
||||
view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewPopup);
|
||||
popup_free(nfc_magic->popup);
|
||||
|
||||
// Loading
|
||||
view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewLoading);
|
||||
loading_free(nfc_magic->loading);
|
||||
|
||||
// TextInput
|
||||
view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewTextInput);
|
||||
text_input_free(nfc_magic->text_input);
|
||||
|
||||
// Custom Widget
|
||||
view_dispatcher_remove_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
|
||||
widget_free(nfc_magic->widget);
|
||||
|
||||
// Worker
|
||||
nfc_magic_worker_stop(nfc_magic->worker);
|
||||
nfc_magic_worker_free(nfc_magic->worker);
|
||||
|
||||
// View Dispatcher
|
||||
view_dispatcher_free(nfc_magic->view_dispatcher);
|
||||
|
||||
// Scene Manager
|
||||
scene_manager_free(nfc_magic->scene_manager);
|
||||
|
||||
// GUI
|
||||
furi_record_close(RECORD_GUI);
|
||||
nfc_magic->gui = NULL;
|
||||
|
||||
// Notifications
|
||||
furi_record_close(RECORD_NOTIFICATION);
|
||||
nfc_magic->notifications = NULL;
|
||||
|
||||
free(nfc_magic);
|
||||
}
|
||||
|
||||
static const NotificationSequence nfc_magic_sequence_blink_start_blue = {
|
||||
&message_blink_start_10,
|
||||
&message_blink_set_color_blue,
|
||||
&message_do_not_reset,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const NotificationSequence nfc_magic_sequence_blink_stop = {
|
||||
&message_blink_stop,
|
||||
NULL,
|
||||
};
|
||||
|
||||
void nfc_magic_blink_start(NfcMagic* nfc_magic) {
|
||||
notification_message(nfc_magic->notifications, &nfc_magic_sequence_blink_start_blue);
|
||||
}
|
||||
|
||||
void nfc_magic_blink_stop(NfcMagic* nfc_magic) {
|
||||
notification_message(nfc_magic->notifications, &nfc_magic_sequence_blink_stop);
|
||||
}
|
||||
|
||||
int32_t nfc_magic_app(void* p) {
|
||||
UNUSED(p);
|
||||
NfcMagic* nfc_magic = nfc_magic_alloc();
|
||||
|
||||
scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneStart);
|
||||
|
||||
view_dispatcher_run(nfc_magic->view_dispatcher);
|
||||
|
||||
nfc_magic_free(nfc_magic);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct NfcMagic NfcMagic;
|
||||
@@ -0,0 +1,77 @@
|
||||
#pragma once
|
||||
|
||||
#include "nfc_magic.h"
|
||||
#include "nfc_magic_worker.h"
|
||||
|
||||
#include "lib/magic/magic.h"
|
||||
|
||||
#include <furi.h>
|
||||
#include <gui/gui.h>
|
||||
#include <gui/view_dispatcher.h>
|
||||
#include <gui/scene_manager.h>
|
||||
#include <notification/notification_messages.h>
|
||||
|
||||
#include <gui/modules/submenu.h>
|
||||
#include <gui/modules/popup.h>
|
||||
#include <gui/modules/loading.h>
|
||||
#include <gui/modules/text_input.h>
|
||||
#include <gui/modules/widget.h>
|
||||
|
||||
#include <input/input.h>
|
||||
|
||||
#include "scenes/nfc_magic_scene.h"
|
||||
|
||||
#include <storage/storage.h>
|
||||
#include <lib/toolbox/path.h>
|
||||
|
||||
#include <lib/nfc/nfc_device.h>
|
||||
#include "nfc_magic_icons.h"
|
||||
|
||||
enum NfcMagicCustomEvent {
|
||||
// Reserve first 100 events for button types and indexes, starting from 0
|
||||
NfcMagicCustomEventReserved = 100,
|
||||
|
||||
NfcMagicCustomEventViewExit,
|
||||
NfcMagicCustomEventWorkerExit,
|
||||
NfcMagicCustomEventByteInputDone,
|
||||
NfcMagicCustomEventTextInputDone,
|
||||
};
|
||||
|
||||
struct NfcMagic {
|
||||
NfcMagicWorker* worker;
|
||||
ViewDispatcher* view_dispatcher;
|
||||
Gui* gui;
|
||||
NotificationApp* notifications;
|
||||
SceneManager* scene_manager;
|
||||
// NfcMagicDevice* dev;
|
||||
NfcDevice* nfc_dev;
|
||||
|
||||
FuriString* text_box_store;
|
||||
|
||||
// Common Views
|
||||
Submenu* submenu;
|
||||
Popup* popup;
|
||||
Loading* loading;
|
||||
TextInput* text_input;
|
||||
Widget* widget;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
NfcMagicViewMenu,
|
||||
NfcMagicViewPopup,
|
||||
NfcMagicViewLoading,
|
||||
NfcMagicViewTextInput,
|
||||
NfcMagicViewWidget,
|
||||
} NfcMagicView;
|
||||
|
||||
NfcMagic* nfc_magic_alloc();
|
||||
|
||||
void nfc_magic_text_store_set(NfcMagic* nfc_magic, const char* text, ...);
|
||||
|
||||
void nfc_magic_text_store_clear(NfcMagic* nfc_magic);
|
||||
|
||||
void nfc_magic_blink_start(NfcMagic* nfc_magic);
|
||||
|
||||
void nfc_magic_blink_stop(NfcMagic* nfc_magic);
|
||||
|
||||
void nfc_magic_show_loading_popup(void* context, bool show);
|
||||
@@ -0,0 +1,174 @@
|
||||
#include "nfc_magic_worker_i.h"
|
||||
|
||||
#include "lib/magic/magic.h"
|
||||
|
||||
#define TAG "NfcMagicWorker"
|
||||
|
||||
static void
|
||||
nfc_magic_worker_change_state(NfcMagicWorker* nfc_magic_worker, NfcMagicWorkerState state) {
|
||||
furi_assert(nfc_magic_worker);
|
||||
|
||||
nfc_magic_worker->state = state;
|
||||
}
|
||||
|
||||
NfcMagicWorker* nfc_magic_worker_alloc() {
|
||||
NfcMagicWorker* nfc_magic_worker = malloc(sizeof(NfcMagicWorker));
|
||||
|
||||
// Worker thread attributes
|
||||
nfc_magic_worker->thread = furi_thread_alloc();
|
||||
furi_thread_set_name(nfc_magic_worker->thread, "NfcMagicWorker");
|
||||
furi_thread_set_stack_size(nfc_magic_worker->thread, 8192);
|
||||
furi_thread_set_callback(nfc_magic_worker->thread, nfc_magic_worker_task);
|
||||
furi_thread_set_context(nfc_magic_worker->thread, nfc_magic_worker);
|
||||
|
||||
nfc_magic_worker->callback = NULL;
|
||||
nfc_magic_worker->context = NULL;
|
||||
|
||||
nfc_magic_worker_change_state(nfc_magic_worker, NfcMagicWorkerStateReady);
|
||||
|
||||
return nfc_magic_worker;
|
||||
}
|
||||
|
||||
void nfc_magic_worker_free(NfcMagicWorker* nfc_magic_worker) {
|
||||
furi_assert(nfc_magic_worker);
|
||||
|
||||
furi_thread_free(nfc_magic_worker->thread);
|
||||
free(nfc_magic_worker);
|
||||
}
|
||||
|
||||
void nfc_magic_worker_stop(NfcMagicWorker* nfc_magic_worker) {
|
||||
furi_assert(nfc_magic_worker);
|
||||
|
||||
nfc_magic_worker_change_state(nfc_magic_worker, NfcMagicWorkerStateStop);
|
||||
furi_thread_join(nfc_magic_worker->thread);
|
||||
}
|
||||
|
||||
void nfc_magic_worker_start(
|
||||
NfcMagicWorker* nfc_magic_worker,
|
||||
NfcMagicWorkerState state,
|
||||
NfcDeviceData* dev_data,
|
||||
NfcMagicWorkerCallback callback,
|
||||
void* context) {
|
||||
furi_assert(nfc_magic_worker);
|
||||
furi_assert(dev_data);
|
||||
|
||||
nfc_magic_worker->callback = callback;
|
||||
nfc_magic_worker->context = context;
|
||||
nfc_magic_worker->dev_data = dev_data;
|
||||
nfc_magic_worker_change_state(nfc_magic_worker, state);
|
||||
furi_thread_start(nfc_magic_worker->thread);
|
||||
}
|
||||
|
||||
int32_t nfc_magic_worker_task(void* context) {
|
||||
NfcMagicWorker* nfc_magic_worker = context;
|
||||
|
||||
if(nfc_magic_worker->state == NfcMagicWorkerStateCheck) {
|
||||
nfc_magic_worker_check(nfc_magic_worker);
|
||||
} else if(nfc_magic_worker->state == NfcMagicWorkerStateWrite) {
|
||||
nfc_magic_worker_write(nfc_magic_worker);
|
||||
} else if(nfc_magic_worker->state == NfcMagicWorkerStateWipe) {
|
||||
nfc_magic_worker_wipe(nfc_magic_worker);
|
||||
}
|
||||
|
||||
nfc_magic_worker_change_state(nfc_magic_worker, NfcMagicWorkerStateReady);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker) {
|
||||
bool card_found_notified = false;
|
||||
FuriHalNfcDevData nfc_data = {};
|
||||
MfClassicData* src_data = &nfc_magic_worker->dev_data->mf_classic_data;
|
||||
|
||||
while(nfc_magic_worker->state == NfcMagicWorkerStateWrite) {
|
||||
if(furi_hal_nfc_detect(&nfc_data, 200)) {
|
||||
if(!card_found_notified) {
|
||||
nfc_magic_worker->callback(
|
||||
NfcMagicWorkerEventCardDetected, nfc_magic_worker->context);
|
||||
card_found_notified = true;
|
||||
}
|
||||
furi_hal_nfc_sleep();
|
||||
|
||||
if(!magic_wupa()) {
|
||||
FURI_LOG_E(TAG, "Not Magic card");
|
||||
nfc_magic_worker->callback(
|
||||
NfcMagicWorkerEventWrongCard, nfc_magic_worker->context);
|
||||
break;
|
||||
}
|
||||
if(!magic_data_access_cmd()) {
|
||||
FURI_LOG_E(TAG, "Not Magic card");
|
||||
nfc_magic_worker->callback(
|
||||
NfcMagicWorkerEventWrongCard, nfc_magic_worker->context);
|
||||
break;
|
||||
}
|
||||
for(size_t i = 0; i < 64; i++) {
|
||||
FURI_LOG_D(TAG, "Writing block %d", i);
|
||||
if(!magic_write_blk(i, &src_data->block[i])) {
|
||||
FURI_LOG_E(TAG, "Failed to write %d block", i);
|
||||
nfc_magic_worker->callback(NfcMagicWorkerEventFail, nfc_magic_worker->context);
|
||||
break;
|
||||
}
|
||||
}
|
||||
nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context);
|
||||
break;
|
||||
} else {
|
||||
if(card_found_notified) {
|
||||
nfc_magic_worker->callback(
|
||||
NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context);
|
||||
card_found_notified = false;
|
||||
}
|
||||
}
|
||||
furi_delay_ms(300);
|
||||
}
|
||||
magic_deactivate();
|
||||
}
|
||||
|
||||
void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker) {
|
||||
bool card_found_notified = false;
|
||||
|
||||
while(nfc_magic_worker->state == NfcMagicWorkerStateCheck) {
|
||||
if(magic_wupa()) {
|
||||
if(!card_found_notified) {
|
||||
nfc_magic_worker->callback(
|
||||
NfcMagicWorkerEventCardDetected, nfc_magic_worker->context);
|
||||
card_found_notified = true;
|
||||
}
|
||||
|
||||
nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context);
|
||||
break;
|
||||
} else {
|
||||
if(card_found_notified) {
|
||||
nfc_magic_worker->callback(
|
||||
NfcMagicWorkerEventNoCardDetected, nfc_magic_worker->context);
|
||||
card_found_notified = false;
|
||||
}
|
||||
}
|
||||
furi_delay_ms(300);
|
||||
}
|
||||
magic_deactivate();
|
||||
}
|
||||
|
||||
void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker) {
|
||||
MfClassicBlock block;
|
||||
memset(&block, 0, sizeof(MfClassicBlock));
|
||||
block.value[0] = 0x01;
|
||||
block.value[1] = 0x02;
|
||||
block.value[2] = 0x03;
|
||||
block.value[3] = 0x04;
|
||||
block.value[4] = 0x04;
|
||||
block.value[5] = 0x08;
|
||||
block.value[6] = 0x04;
|
||||
|
||||
while(nfc_magic_worker->state == NfcMagicWorkerStateWipe) {
|
||||
magic_deactivate();
|
||||
furi_delay_ms(300);
|
||||
if(!magic_wupa()) continue;
|
||||
if(!magic_wipe()) continue;
|
||||
if(!magic_data_access_cmd()) continue;
|
||||
if(!magic_write_blk(0, &block)) continue;
|
||||
nfc_magic_worker->callback(NfcMagicWorkerEventSuccess, nfc_magic_worker->context);
|
||||
magic_deactivate();
|
||||
break;
|
||||
}
|
||||
magic_deactivate();
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/nfc/nfc_device.h>
|
||||
|
||||
typedef struct NfcMagicWorker NfcMagicWorker;
|
||||
|
||||
typedef enum {
|
||||
NfcMagicWorkerStateReady,
|
||||
|
||||
NfcMagicWorkerStateCheck,
|
||||
NfcMagicWorkerStateWrite,
|
||||
NfcMagicWorkerStateWipe,
|
||||
|
||||
NfcMagicWorkerStateStop,
|
||||
} NfcMagicWorkerState;
|
||||
|
||||
typedef enum {
|
||||
NfcMagicWorkerEventSuccess,
|
||||
NfcMagicWorkerEventFail,
|
||||
NfcMagicWorkerEventCardDetected,
|
||||
NfcMagicWorkerEventNoCardDetected,
|
||||
NfcMagicWorkerEventWrongCard,
|
||||
} NfcMagicWorkerEvent;
|
||||
|
||||
typedef bool (*NfcMagicWorkerCallback)(NfcMagicWorkerEvent event, void* context);
|
||||
|
||||
NfcMagicWorker* nfc_magic_worker_alloc();
|
||||
|
||||
void nfc_magic_worker_free(NfcMagicWorker* nfc_magic_worker);
|
||||
|
||||
void nfc_magic_worker_stop(NfcMagicWorker* nfc_magic_worker);
|
||||
|
||||
void nfc_magic_worker_start(
|
||||
NfcMagicWorker* nfc_magic_worker,
|
||||
NfcMagicWorkerState state,
|
||||
NfcDeviceData* dev_data,
|
||||
NfcMagicWorkerCallback callback,
|
||||
void* context);
|
||||
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
#include "nfc_magic_worker.h"
|
||||
|
||||
struct NfcMagicWorker {
|
||||
FuriThread* thread;
|
||||
|
||||
NfcDeviceData* dev_data;
|
||||
|
||||
NfcMagicWorkerCallback callback;
|
||||
void* context;
|
||||
|
||||
NfcMagicWorkerState state;
|
||||
};
|
||||
|
||||
int32_t nfc_magic_worker_task(void* context);
|
||||
|
||||
void nfc_magic_worker_check(NfcMagicWorker* nfc_magic_worker);
|
||||
|
||||
void nfc_magic_worker_write(NfcMagicWorker* nfc_magic_worker);
|
||||
|
||||
void nfc_magic_worker_wipe(NfcMagicWorker* nfc_magic_worker);
|
||||
@@ -0,0 +1,30 @@
|
||||
#include "nfc_magic_scene.h"
|
||||
|
||||
// Generate scene on_enter handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
|
||||
void (*const nfc_magic_on_enter_handlers[])(void*) = {
|
||||
#include "nfc_magic_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_event,
|
||||
bool (*const nfc_magic_on_event_handlers[])(void* context, SceneManagerEvent event) = {
|
||||
#include "nfc_magic_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers array
|
||||
#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_exit,
|
||||
void (*const nfc_magic_on_exit_handlers[])(void* context) = {
|
||||
#include "nfc_magic_scene_config.h"
|
||||
};
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Initialize scene handlers configuration structure
|
||||
const SceneManagerHandlers nfc_magic_scene_handlers = {
|
||||
.on_enter_handlers = nfc_magic_on_enter_handlers,
|
||||
.on_event_handlers = nfc_magic_on_event_handlers,
|
||||
.on_exit_handlers = nfc_magic_on_exit_handlers,
|
||||
.scene_num = NfcMagicSceneNum,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <gui/scene_manager.h>
|
||||
|
||||
// Generate scene id and total number
|
||||
#define ADD_SCENE(prefix, name, id) NfcMagicScene##id,
|
||||
typedef enum {
|
||||
#include "nfc_magic_scene_config.h"
|
||||
NfcMagicSceneNum,
|
||||
} NfcMagicScene;
|
||||
#undef ADD_SCENE
|
||||
|
||||
extern const SceneManagerHandlers nfc_magic_scene_handlers;
|
||||
|
||||
// Generate scene on_enter handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
|
||||
#include "nfc_magic_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_event handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) \
|
||||
bool prefix##_scene_##name##_on_event(void* context, SceneManagerEvent event);
|
||||
#include "nfc_magic_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
|
||||
// Generate scene on_exit handlers declaration
|
||||
#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_exit(void* context);
|
||||
#include "nfc_magic_scene_config.h"
|
||||
#undef ADD_SCENE
|
||||
@@ -0,0 +1,87 @@
|
||||
#include "../nfc_magic_i.h"
|
||||
|
||||
enum {
|
||||
NfcMagicSceneCheckStateCardSearch,
|
||||
NfcMagicSceneCheckStateCardFound,
|
||||
};
|
||||
|
||||
bool nfc_magic_check_worker_callback(NfcMagicWorkerEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
NfcMagic* nfc_magic = context;
|
||||
view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void nfc_magic_scene_check_setup_view(NfcMagic* nfc_magic) {
|
||||
Popup* popup = nfc_magic->popup;
|
||||
popup_reset(popup);
|
||||
uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneCheck);
|
||||
|
||||
if(state == NfcMagicSceneCheckStateCardSearch) {
|
||||
popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50);
|
||||
popup_set_text(
|
||||
nfc_magic->popup, "Apply card to\nthe back", 128, 32, AlignRight, AlignCenter);
|
||||
} else {
|
||||
popup_set_icon(popup, 12, 23, &I_Loading_24);
|
||||
popup_set_header(popup, "Checking\nDon't move...", 52, 32, AlignLeft, AlignCenter);
|
||||
}
|
||||
|
||||
view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup);
|
||||
}
|
||||
|
||||
void nfc_magic_scene_check_on_enter(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardSearch);
|
||||
nfc_magic_scene_check_setup_view(nfc_magic);
|
||||
|
||||
// Setup and start worker
|
||||
nfc_magic_worker_start(
|
||||
nfc_magic->worker,
|
||||
NfcMagicWorkerStateCheck,
|
||||
&nfc_magic->nfc_dev->dev_data,
|
||||
nfc_magic_check_worker_callback,
|
||||
nfc_magic);
|
||||
nfc_magic_blink_start(nfc_magic);
|
||||
}
|
||||
|
||||
bool nfc_magic_scene_check_on_event(void* context, SceneManagerEvent event) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcMagicWorkerEventSuccess) {
|
||||
scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneMagicInfo);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcMagicWorkerEventWrongCard) {
|
||||
scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNotMagic);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcMagicWorkerEventCardDetected) {
|
||||
scene_manager_set_scene_state(
|
||||
nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardFound);
|
||||
nfc_magic_scene_check_setup_view(nfc_magic);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcMagicWorkerEventNoCardDetected) {
|
||||
scene_manager_set_scene_state(
|
||||
nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardSearch);
|
||||
nfc_magic_scene_check_setup_view(nfc_magic);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_magic_scene_check_on_exit(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
|
||||
nfc_magic_worker_stop(nfc_magic->worker);
|
||||
scene_manager_set_scene_state(
|
||||
nfc_magic->scene_manager, NfcMagicSceneCheck, NfcMagicSceneCheckStateCardSearch);
|
||||
// Clear view
|
||||
popup_reset(nfc_magic->popup);
|
||||
|
||||
nfc_magic_blink_stop(nfc_magic);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
ADD_SCENE(nfc_magic, start, Start)
|
||||
ADD_SCENE(nfc_magic, file_select, FileSelect)
|
||||
ADD_SCENE(nfc_magic, write_confirm, WriteConfirm)
|
||||
ADD_SCENE(nfc_magic, wrong_card, WrongCard)
|
||||
ADD_SCENE(nfc_magic, write, Write)
|
||||
ADD_SCENE(nfc_magic, write_fail, WriteFail)
|
||||
ADD_SCENE(nfc_magic, success, Success)
|
||||
ADD_SCENE(nfc_magic, check, Check)
|
||||
ADD_SCENE(nfc_magic, not_magic, NotMagic)
|
||||
ADD_SCENE(nfc_magic, magic_info, MagicInfo)
|
||||
ADD_SCENE(nfc_magic, wipe, Wipe)
|
||||
ADD_SCENE(nfc_magic, wipe_fail, WipeFail)
|
||||
@@ -0,0 +1,34 @@
|
||||
#include "../nfc_magic_i.h"
|
||||
|
||||
static bool nfc_magic_scene_file_select_is_file_suitable(NfcDevice* nfc_dev) {
|
||||
return (nfc_dev->format == NfcDeviceSaveFormatMifareClassic) &&
|
||||
(nfc_dev->dev_data.mf_classic_data.type == MfClassicType1k) &&
|
||||
(nfc_dev->dev_data.nfc_data.uid_len == 4);
|
||||
}
|
||||
|
||||
void nfc_magic_scene_file_select_on_enter(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
// Process file_select return
|
||||
nfc_device_set_loading_callback(nfc_magic->nfc_dev, nfc_magic_show_loading_popup, nfc_magic);
|
||||
|
||||
if(nfc_file_select(nfc_magic->nfc_dev)) {
|
||||
if(nfc_magic_scene_file_select_is_file_suitable(nfc_magic->nfc_dev)) {
|
||||
scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWriteConfirm);
|
||||
} else {
|
||||
scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWrongCard);
|
||||
}
|
||||
} else {
|
||||
scene_manager_previous_scene(nfc_magic->scene_manager);
|
||||
}
|
||||
}
|
||||
|
||||
bool nfc_magic_scene_file_select_on_event(void* context, SceneManagerEvent event) {
|
||||
UNUSED(context);
|
||||
UNUSED(event);
|
||||
return false;
|
||||
}
|
||||
|
||||
void nfc_magic_scene_file_select_on_exit(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
nfc_device_set_loading_callback(nfc_magic->nfc_dev, NULL, nfc_magic);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#include "../nfc_magic_i.h"
|
||||
|
||||
void nfc_magic_scene_magic_info_widget_callback(
|
||||
GuiButtonType result,
|
||||
InputType type,
|
||||
void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_magic_scene_magic_info_on_enter(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
Widget* widget = nfc_magic->widget;
|
||||
|
||||
notification_message(nfc_magic->notifications, &sequence_success);
|
||||
|
||||
widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48);
|
||||
widget_add_string_element(
|
||||
widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Magic card detected");
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_magic_info_widget_callback, nfc_magic);
|
||||
|
||||
// Setup and start worker
|
||||
view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
|
||||
}
|
||||
|
||||
bool nfc_magic_scene_magic_info_on_event(void* context, SceneManagerEvent event) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
consumed = scene_manager_previous_scene(nfc_magic->scene_manager);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_magic_scene_magic_info_on_exit(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
|
||||
widget_reset(nfc_magic->widget);
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "../nfc_magic_i.h"
|
||||
|
||||
void nfc_magic_scene_not_magic_widget_callback(GuiButtonType result, InputType type, void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_magic_scene_not_magic_on_enter(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
Widget* widget = nfc_magic->widget;
|
||||
|
||||
notification_message(nfc_magic->notifications, &sequence_error);
|
||||
|
||||
// widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48);
|
||||
widget_add_string_element(
|
||||
widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card");
|
||||
widget_add_string_multiline_element(
|
||||
widget, 4, 17, AlignLeft, AlignTop, FontSecondary, "Not a magic\ncard");
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_not_magic_widget_callback, nfc_magic);
|
||||
|
||||
// Setup and start worker
|
||||
view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
|
||||
}
|
||||
|
||||
bool nfc_magic_scene_not_magic_on_event(void* context, SceneManagerEvent event) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
consumed = scene_manager_previous_scene(nfc_magic->scene_manager);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_magic_scene_not_magic_on_exit(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
|
||||
widget_reset(nfc_magic->widget);
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
#include "../nfc_magic_i.h"
|
||||
enum SubmenuIndex {
|
||||
SubmenuIndexCheck,
|
||||
SubmenuIndexWriteGen1A,
|
||||
SubmenuIndexWipe,
|
||||
};
|
||||
|
||||
void nfc_magic_scene_start_submenu_callback(void* context, uint32_t index) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, index);
|
||||
}
|
||||
|
||||
void nfc_magic_scene_start_on_enter(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
|
||||
Submenu* submenu = nfc_magic->submenu;
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Check Magic Tag",
|
||||
SubmenuIndexCheck,
|
||||
nfc_magic_scene_start_submenu_callback,
|
||||
nfc_magic);
|
||||
submenu_add_item(
|
||||
submenu,
|
||||
"Write Gen1A",
|
||||
SubmenuIndexWriteGen1A,
|
||||
nfc_magic_scene_start_submenu_callback,
|
||||
nfc_magic);
|
||||
submenu_add_item(
|
||||
submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_start_submenu_callback, nfc_magic);
|
||||
|
||||
submenu_set_selected_item(
|
||||
submenu, scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneStart));
|
||||
view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewMenu);
|
||||
}
|
||||
|
||||
bool nfc_magic_scene_start_on_event(void* context, SceneManagerEvent event) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == SubmenuIndexCheck) {
|
||||
scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneCheck);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexWriteGen1A) {
|
||||
scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneFileSelect);
|
||||
consumed = true;
|
||||
} else if(event.event == SubmenuIndexWipe) {
|
||||
scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipe);
|
||||
consumed = true;
|
||||
}
|
||||
scene_manager_set_scene_state(nfc_magic->scene_manager, NfcMagicSceneStart, event.event);
|
||||
}
|
||||
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_magic_scene_start_on_exit(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
submenu_reset(nfc_magic->submenu);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#include "../nfc_magic_i.h"
|
||||
|
||||
void nfc_magic_scene_success_popup_callback(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, NfcMagicCustomEventViewExit);
|
||||
}
|
||||
|
||||
void nfc_magic_scene_success_on_enter(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
|
||||
notification_message(nfc_magic->notifications, &sequence_success);
|
||||
|
||||
Popup* popup = nfc_magic->popup;
|
||||
popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59);
|
||||
popup_set_header(popup, "Success!", 10, 20, AlignLeft, AlignBottom);
|
||||
popup_set_timeout(popup, 1500);
|
||||
popup_set_context(popup, nfc_magic);
|
||||
popup_set_callback(popup, nfc_magic_scene_success_popup_callback);
|
||||
popup_enable_timeout(popup);
|
||||
|
||||
view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup);
|
||||
}
|
||||
|
||||
bool nfc_magic_scene_success_on_event(void* context, SceneManagerEvent event) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcMagicCustomEventViewExit) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc_magic->scene_manager, NfcMagicSceneStart);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_magic_scene_success_on_exit(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
|
||||
// Clear view
|
||||
popup_reset(nfc_magic->popup);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
#include "../nfc_magic_i.h"
|
||||
|
||||
enum {
|
||||
NfcMagicSceneWipeStateCardSearch,
|
||||
NfcMagicSceneWipeStateCardFound,
|
||||
};
|
||||
|
||||
bool nfc_magic_wipe_worker_callback(NfcMagicWorkerEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
NfcMagic* nfc_magic = context;
|
||||
view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void nfc_magic_scene_wipe_setup_view(NfcMagic* nfc_magic) {
|
||||
Popup* popup = nfc_magic->popup;
|
||||
popup_reset(popup);
|
||||
uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneWipe);
|
||||
|
||||
if(state == NfcMagicSceneWipeStateCardSearch) {
|
||||
popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50);
|
||||
popup_set_text(
|
||||
nfc_magic->popup, "Apply card to\nthe back", 128, 32, AlignRight, AlignCenter);
|
||||
} else {
|
||||
popup_set_icon(popup, 12, 23, &I_Loading_24);
|
||||
popup_set_header(popup, "Wiping\nDon't move...", 52, 32, AlignLeft, AlignCenter);
|
||||
}
|
||||
|
||||
view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup);
|
||||
}
|
||||
|
||||
void nfc_magic_scene_wipe_on_enter(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch);
|
||||
nfc_magic_scene_wipe_setup_view(nfc_magic);
|
||||
|
||||
// Setup and start worker
|
||||
nfc_magic_worker_start(
|
||||
nfc_magic->worker,
|
||||
NfcMagicWorkerStateWipe,
|
||||
&nfc_magic->nfc_dev->dev_data,
|
||||
nfc_magic_wipe_worker_callback,
|
||||
nfc_magic);
|
||||
nfc_magic_blink_start(nfc_magic);
|
||||
}
|
||||
|
||||
bool nfc_magic_scene_wipe_on_event(void* context, SceneManagerEvent event) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcMagicWorkerEventSuccess) {
|
||||
scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneSuccess);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcMagicWorkerEventFail) {
|
||||
scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWipeFail);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcMagicWorkerEventWrongCard) {
|
||||
scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNotMagic);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcMagicWorkerEventCardDetected) {
|
||||
scene_manager_set_scene_state(
|
||||
nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardFound);
|
||||
nfc_magic_scene_wipe_setup_view(nfc_magic);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcMagicWorkerEventNoCardDetected) {
|
||||
scene_manager_set_scene_state(
|
||||
nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch);
|
||||
nfc_magic_scene_wipe_setup_view(nfc_magic);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_magic_scene_wipe_on_exit(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
|
||||
nfc_magic_worker_stop(nfc_magic->worker);
|
||||
scene_manager_set_scene_state(
|
||||
nfc_magic->scene_manager, NfcMagicSceneWipe, NfcMagicSceneWipeStateCardSearch);
|
||||
// Clear view
|
||||
popup_reset(nfc_magic->popup);
|
||||
|
||||
nfc_magic_blink_stop(nfc_magic);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
#include "../nfc_magic_i.h"
|
||||
|
||||
void nfc_magic_scene_wipe_fail_widget_callback(GuiButtonType result, InputType type, void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_magic_scene_wipe_fail_on_enter(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
Widget* widget = nfc_magic->widget;
|
||||
|
||||
notification_message(nfc_magic->notifications, &sequence_error);
|
||||
|
||||
widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48);
|
||||
widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Wipe failed");
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wipe_fail_widget_callback, nfc_magic);
|
||||
|
||||
// Setup and start worker
|
||||
view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
|
||||
}
|
||||
|
||||
bool nfc_magic_scene_wipe_fail_on_event(void* context, SceneManagerEvent event) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
consumed = scene_manager_previous_scene(nfc_magic->scene_manager);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_magic_scene_wipe_fail_on_exit(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
|
||||
widget_reset(nfc_magic->widget);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
#include "../nfc_magic_i.h"
|
||||
|
||||
enum {
|
||||
NfcMagicSceneWriteStateCardSearch,
|
||||
NfcMagicSceneWriteStateCardFound,
|
||||
};
|
||||
|
||||
bool nfc_magic_write_worker_callback(NfcMagicWorkerEvent event, void* context) {
|
||||
furi_assert(context);
|
||||
|
||||
NfcMagic* nfc_magic = context;
|
||||
view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, event);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void nfc_magic_scene_write_setup_view(NfcMagic* nfc_magic) {
|
||||
Popup* popup = nfc_magic->popup;
|
||||
popup_reset(popup);
|
||||
uint32_t state = scene_manager_get_scene_state(nfc_magic->scene_manager, NfcMagicSceneWrite);
|
||||
|
||||
if(state == NfcMagicSceneWriteStateCardSearch) {
|
||||
popup_set_text(
|
||||
nfc_magic->popup, "Apply card to\nthe back", 128, 32, AlignRight, AlignCenter);
|
||||
popup_set_icon(nfc_magic->popup, 0, 8, &I_NFC_manual_60x50);
|
||||
} else {
|
||||
popup_set_icon(popup, 12, 23, &I_Loading_24);
|
||||
popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter);
|
||||
}
|
||||
|
||||
view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewPopup);
|
||||
}
|
||||
|
||||
void nfc_magic_scene_write_on_enter(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
|
||||
scene_manager_set_scene_state(
|
||||
nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch);
|
||||
nfc_magic_scene_write_setup_view(nfc_magic);
|
||||
|
||||
// Setup and start worker
|
||||
nfc_magic_worker_start(
|
||||
nfc_magic->worker,
|
||||
NfcMagicWorkerStateWrite,
|
||||
&nfc_magic->nfc_dev->dev_data,
|
||||
nfc_magic_write_worker_callback,
|
||||
nfc_magic);
|
||||
nfc_magic_blink_start(nfc_magic);
|
||||
}
|
||||
|
||||
bool nfc_magic_scene_write_on_event(void* context, SceneManagerEvent event) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == NfcMagicWorkerEventSuccess) {
|
||||
scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneSuccess);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcMagicWorkerEventFail) {
|
||||
scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWriteFail);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcMagicWorkerEventWrongCard) {
|
||||
scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneNotMagic);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcMagicWorkerEventCardDetected) {
|
||||
scene_manager_set_scene_state(
|
||||
nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardFound);
|
||||
nfc_magic_scene_write_setup_view(nfc_magic);
|
||||
consumed = true;
|
||||
} else if(event.event == NfcMagicWorkerEventNoCardDetected) {
|
||||
scene_manager_set_scene_state(
|
||||
nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch);
|
||||
nfc_magic_scene_write_setup_view(nfc_magic);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_magic_scene_write_on_exit(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
|
||||
nfc_magic_worker_stop(nfc_magic->worker);
|
||||
scene_manager_set_scene_state(
|
||||
nfc_magic->scene_manager, NfcMagicSceneWrite, NfcMagicSceneWriteStateCardSearch);
|
||||
// Clear view
|
||||
popup_reset(nfc_magic->popup);
|
||||
|
||||
nfc_magic_blink_stop(nfc_magic);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
#include "../nfc_magic_i.h"
|
||||
|
||||
void nfc_magic_scene_write_confirm_widget_callback(
|
||||
GuiButtonType result,
|
||||
InputType type,
|
||||
void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_magic_scene_write_confirm_on_enter(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
Widget* widget = nfc_magic->widget;
|
||||
|
||||
widget_add_string_element(widget, 3, 0, AlignLeft, AlignTop, FontPrimary, "Risky operation");
|
||||
widget_add_text_box_element(
|
||||
widget,
|
||||
0,
|
||||
13,
|
||||
128,
|
||||
54,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
"Writing to this card will change manufacturer block. On some cards it may not be rewritten",
|
||||
false);
|
||||
widget_add_button_element(
|
||||
widget,
|
||||
GuiButtonTypeCenter,
|
||||
"Continue",
|
||||
nfc_magic_scene_write_confirm_widget_callback,
|
||||
nfc_magic);
|
||||
widget_add_button_element(
|
||||
widget,
|
||||
GuiButtonTypeLeft,
|
||||
"Back",
|
||||
nfc_magic_scene_write_confirm_widget_callback,
|
||||
nfc_magic);
|
||||
|
||||
// Setup and start worker
|
||||
view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
|
||||
}
|
||||
|
||||
bool nfc_magic_scene_write_confirm_on_event(void* context, SceneManagerEvent event) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
consumed = scene_manager_previous_scene(nfc_magic->scene_manager);
|
||||
} else if(event.event == GuiButtonTypeCenter) {
|
||||
scene_manager_next_scene(nfc_magic->scene_manager, NfcMagicSceneWrite);
|
||||
consumed = true;
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_magic_scene_write_confirm_on_exit(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
|
||||
widget_reset(nfc_magic->widget);
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#include "../nfc_magic_i.h"
|
||||
|
||||
void nfc_magic_scene_write_fail_widget_callback(
|
||||
GuiButtonType result,
|
||||
InputType type,
|
||||
void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_magic_scene_write_fail_on_enter(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
Widget* widget = nfc_magic->widget;
|
||||
|
||||
notification_message(nfc_magic->notifications, &sequence_error);
|
||||
|
||||
widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48);
|
||||
widget_add_string_element(
|
||||
widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!");
|
||||
widget_add_string_multiline_element(
|
||||
widget,
|
||||
7,
|
||||
17,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
"Not all sectors\nwere written\ncorrectly.");
|
||||
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeLeft, "Finish", nfc_magic_scene_write_fail_widget_callback, nfc_magic);
|
||||
|
||||
// Setup and start worker
|
||||
view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
|
||||
}
|
||||
|
||||
bool nfc_magic_scene_write_fail_on_event(void* context, SceneManagerEvent event) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc_magic->scene_manager, NfcMagicSceneStart);
|
||||
}
|
||||
} else if(event.type == SceneManagerEventTypeBack) {
|
||||
consumed = scene_manager_search_and_switch_to_previous_scene(
|
||||
nfc_magic->scene_manager, NfcMagicSceneStart);
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_magic_scene_write_fail_on_exit(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
|
||||
widget_reset(nfc_magic->widget);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
#include "../nfc_magic_i.h"
|
||||
|
||||
void nfc_magic_scene_wrong_card_widget_callback(
|
||||
GuiButtonType result,
|
||||
InputType type,
|
||||
void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
if(type == InputTypeShort) {
|
||||
view_dispatcher_send_custom_event(nfc_magic->view_dispatcher, result);
|
||||
}
|
||||
}
|
||||
|
||||
void nfc_magic_scene_wrong_card_on_enter(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
Widget* widget = nfc_magic->widget;
|
||||
|
||||
notification_message(nfc_magic->notifications, &sequence_error);
|
||||
|
||||
widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48);
|
||||
widget_add_string_element(
|
||||
widget, 1, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card");
|
||||
widget_add_string_multiline_element(
|
||||
widget,
|
||||
1,
|
||||
17,
|
||||
AlignLeft,
|
||||
AlignTop,
|
||||
FontSecondary,
|
||||
"Writing is supported\nonly for 4 bytes UID\nMifare CLassic 1k");
|
||||
widget_add_button_element(
|
||||
widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wrong_card_widget_callback, nfc_magic);
|
||||
|
||||
// Setup and start worker
|
||||
view_dispatcher_switch_to_view(nfc_magic->view_dispatcher, NfcMagicViewWidget);
|
||||
}
|
||||
|
||||
bool nfc_magic_scene_wrong_card_on_event(void* context, SceneManagerEvent event) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
bool consumed = false;
|
||||
|
||||
if(event.type == SceneManagerEventTypeCustom) {
|
||||
if(event.event == GuiButtonTypeLeft) {
|
||||
consumed = scene_manager_previous_scene(nfc_magic->scene_manager);
|
||||
}
|
||||
}
|
||||
return consumed;
|
||||
}
|
||||
|
||||
void nfc_magic_scene_wrong_card_on_exit(void* context) {
|
||||
NfcMagic* nfc_magic = context;
|
||||
|
||||
widget_reset(nfc_magic->widget);
|
||||
}
|
||||
@@ -399,6 +399,8 @@ int32_t nrfsniff_app(void* p) {
|
||||
case InputKeyBack:
|
||||
if(event.input.type == InputTypeLong) processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -440,7 +442,7 @@ int32_t nrfsniff_app(void* p) {
|
||||
sample_time = DEFAULT_SAMPLE_TIME;
|
||||
target_rate = 8; // rate can be either 8 (2Mbps) or 0 (1Mbps)
|
||||
sniffing_state = false;
|
||||
furi_hal_spi_release(nrf24_HANDLE);
|
||||
nrf24_deinit();
|
||||
view_port_enabled_set(view_port, false);
|
||||
gui_remove_view_port(gui, view_port);
|
||||
furi_record_close(RECORD_GUI);
|
||||
|
||||
|
After Width: | Height: | Size: 308 B |
@@ -9,7 +9,7 @@ App(
|
||||
],
|
||||
stack_size=4 * 1024,
|
||||
order=30,
|
||||
fap_icon="../../../assets/icons/Archive/125_10px.png",
|
||||
fap_icon="125_10px.png",
|
||||
fap_category="Tools",
|
||||
fap_libs=["mbedtls"],
|
||||
fap_private_libs=[
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
#include <furi_hal.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <st25r3916.h>
|
||||
#include <rfal_analogConfig.h>
|
||||
#include <rfal_rf.h>
|
||||
|
||||
#include <platform.h>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "rfal_picopass.h"
|
||||
#include "utils.h"
|
||||
|
||||
#define RFAL_PICOPASS_TXRX_FLAGS \
|
||||
(FURI_HAL_NFC_LL_TXRX_FLAGS_CRC_TX_MANUAL | FURI_HAL_NFC_LL_TXRX_FLAGS_AGC_ON | \
|
||||
@@ -97,7 +96,7 @@ FuriHalNfcReturn rfalPicoPassPollerSelect(uint8_t* csn, rfalPicoPassSelectRes* s
|
||||
|
||||
rfalPicoPassSelectReq selReq;
|
||||
selReq.CMD = RFAL_PICOPASS_CMD_SELECT;
|
||||
ST_MEMCPY(selReq.CSN, csn, RFAL_PICOPASS_UID_LEN);
|
||||
memcpy(selReq.CSN, csn, RFAL_PICOPASS_UID_LEN);
|
||||
uint16_t recvLen = 0;
|
||||
uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
|
||||
uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
|
||||
@@ -146,8 +145,8 @@ FuriHalNfcReturn rfalPicoPassPollerCheck(uint8_t* mac, rfalPicoPassCheckRes* chk
|
||||
FuriHalNfcReturn ret;
|
||||
rfalPicoPassCheckReq chkReq;
|
||||
chkReq.CMD = RFAL_PICOPASS_CMD_CHECK;
|
||||
ST_MEMCPY(chkReq.mac, mac, 4);
|
||||
ST_MEMSET(chkReq.null, 0, 4);
|
||||
memcpy(chkReq.mac, mac, 4);
|
||||
memset(chkReq.null, 0, 4);
|
||||
uint16_t recvLen = 0;
|
||||
uint32_t flags = RFAL_PICOPASS_TXRX_FLAGS;
|
||||
uint32_t fwt = furi_hal_nfc_ll_ms2fc(20);
|
||||
|
||||
@@ -143,6 +143,8 @@ int32_t sentry_safe_app(void* p) {
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,8 +196,8 @@ static Point snake_game_get_new_fruit(SnakeState const* const snake_state) {
|
||||
if((buffer[y] & mask) == 0) {
|
||||
if(newFruit == 0) {
|
||||
Point p = {
|
||||
.x = x,
|
||||
.y = y,
|
||||
.x = x * 2,
|
||||
.y = y * 2,
|
||||
};
|
||||
return p;
|
||||
}
|
||||
@@ -315,7 +315,6 @@ static void
|
||||
|
||||
int32_t snake_game_app(void* p) {
|
||||
UNUSED(p);
|
||||
srand(DWT->CYCCNT);
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(SnakeEvent));
|
||||
|
||||
@@ -378,6 +377,8 @@ int32_t snake_game_app(void* p) {
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(event.type == EventTypeTick) {
|
||||
|
||||
@@ -496,6 +496,8 @@ int32_t spectrum_analyzer_app(void* p) {
|
||||
case InputKeyBack:
|
||||
exit_loop = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
furi_mutex_release(spectrum_analyzer->model_mutex);
|
||||
|
||||
@@ -345,8 +345,6 @@ static void
|
||||
}
|
||||
|
||||
int32_t tetris_game_app() {
|
||||
srand(DWT->CYCCNT);
|
||||
|
||||
FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(TetrisEvent));
|
||||
|
||||
TetrisState* tetris_state = malloc(sizeof(TetrisState));
|
||||
@@ -437,6 +435,8 @@ int32_t tetris_game_app() {
|
||||
case InputKeyBack:
|
||||
processing = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if(event.type == EventTypeTick) {
|
||||
|
||||
@@ -358,12 +358,11 @@ int32_t tictactoe_game_app(void* p) {
|
||||
case InputKeyOk:
|
||||
tictactoe_state->button_state = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Event timeout
|
||||
FURI_LOG_D(TAG, "furi_message_queue: Event timeout");
|
||||
}
|
||||
|
||||
view_port_update(view_port);
|
||||
|
||||
@@ -282,6 +282,8 @@ bool totp_scene_add_new_token_handle_event(PluginEvent* const event, PluginState
|
||||
totp_scene_director_activate_scene(plugin_state, TotpSceneGenerateToken, NULL);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +158,8 @@ bool totp_scene_app_settings_handle_event(PluginEvent* const event, PluginState*
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +143,8 @@ bool totp_scene_authenticate_handle_event(PluginEvent* const event, PluginState*
|
||||
scene_state->code_length--;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -303,6 +303,8 @@ bool totp_scene_generate_token_handle_event(PluginEvent* const event, PluginStat
|
||||
break;
|
||||
case InputKeyBack:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,6 +179,8 @@ bool totp_scene_token_menu_handle_event(PluginEvent* const event, PluginState* p
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include <furi.h>
|
||||
#include <furi_hal.h>
|
||||
|
||||
#define WS_VERSION_APP "0.3.1"
|
||||
#define WS_VERSION_APP "0.4"
|
||||
#define WS_DEVELOPED "SkorP"
|
||||
#define WS_GITHUB "https://github.com/flipperdevices/flipperzero-firmware"
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/*
|
||||
* Help
|
||||
* https://github.com/merbanan/rtl_433/blob/5bef4e43133ac4c0e2d18d36f87c52b4f9458453/src/devices/acurite.c
|
||||
* https://github.com/merbanan/rtl_433/blob/master/src/devices/acurite.c
|
||||
*
|
||||
* Acurite 592TXR Temperature Humidity sensor decoder
|
||||
* Message Type 0x04, 7 bytes
|
||||
@@ -293,7 +293,7 @@ void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* ou
|
||||
"%s %dbit\r\n"
|
||||
"Key:0x%lX%08lX\r\n"
|
||||
"Sn:0x%lX Ch:%d Bat:%d\r\n"
|
||||
"Temp:%d.%d C Hum:%d%%",
|
||||
"Temp:%3.1f C Hum:%d%%",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32),
|
||||
@@ -301,7 +301,6 @@ void ws_protocol_decoder_acurite_592txr_get_string(void* context, FuriString* ou
|
||||
instance->generic.id,
|
||||
instance->generic.channel,
|
||||
instance->generic.battery_low,
|
||||
(int16_t)instance->generic.temp,
|
||||
abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))),
|
||||
(double)instance->generic.temp,
|
||||
instance->generic.humidity);
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* out
|
||||
"%s %dbit\r\n"
|
||||
"Key:0x%lX%08lX\r\n"
|
||||
"Sn:0x%lX Ch:%d Bat:%d\r\n"
|
||||
"Temp:%d.%d C Hum:%d%%",
|
||||
"Temp:%3.1f C Hum:%d%%",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32),
|
||||
@@ -242,7 +242,6 @@ void ws_protocol_decoder_acurite_606tx_get_string(void* context, FuriString* out
|
||||
instance->generic.id,
|
||||
instance->generic.channel,
|
||||
instance->generic.battery_low,
|
||||
(int16_t)instance->generic.temp,
|
||||
abs(((int16_t)(instance->generic.temp * 10) - (((int16_t)instance->generic.temp) * 10))),
|
||||
(double)instance->generic.temp,
|
||||
instance->generic.humidity);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -0,0 +1,276 @@
|
||||
#include "ambient_weather.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
#define TAG "WSProtocolAmbient_Weather"
|
||||
|
||||
/*
|
||||
* Help
|
||||
* https://github.com/merbanan/rtl_433/blob/master/src/devices/ambient_weather.c
|
||||
*
|
||||
* Decode Ambient Weather F007TH, F012TH, TF 30.3208.02, SwitchDoc F016TH.
|
||||
* Devices supported:
|
||||
* - Ambient Weather F007TH Thermo-Hygrometer.
|
||||
* - Ambient Weather F012TH Indoor/Display Thermo-Hygrometer.
|
||||
* - TFA senders 30.3208.02 from the TFA "Klima-Monitor" 30.3054,
|
||||
* - SwitchDoc Labs F016TH.
|
||||
* This decoder handles the 433mhz/868mhz thermo-hygrometers.
|
||||
* The 915mhz (WH*) family of devices use different modulation/encoding.
|
||||
* Byte 0 Byte 1 Byte 2 Byte 3 Byte 4 Byte 5
|
||||
* xxxxMMMM IIIIIIII BCCCTTTT TTTTTTTT HHHHHHHH MMMMMMMM
|
||||
* - x: Unknown 0x04 on F007TH/F012TH
|
||||
* - M: Model Number?, 0x05 on F007TH/F012TH/SwitchDocLabs F016TH
|
||||
* - I: ID byte (8 bits), volatie, changes at power up,
|
||||
* - B: Battery Low
|
||||
* - C: Channel (3 bits 1-8) - F007TH set by Dip switch, F012TH soft setting
|
||||
* - T: Temperature 12 bits - Fahrenheit * 10 + 400
|
||||
* - H: Humidity (8 bits)
|
||||
* - M: Message integrity check LFSR Digest-8, gen 0x98, key 0x3e, init 0x64
|
||||
*
|
||||
* three repeats without gap
|
||||
* full preamble is 0x00145 (the last bits might not be fixed, e.g. 0x00146)
|
||||
* and on decoding also 0xffd45
|
||||
*/
|
||||
|
||||
#define AMBIENT_WEATHER_PACKET_HEADER_1 0xFFD440000000000 //0xffd45 .. 0xffd46
|
||||
#define AMBIENT_WEATHER_PACKET_HEADER_2 0x001440000000000 //0x00145 .. 0x00146
|
||||
#define AMBIENT_WEATHER_PACKET_HEADER_MASK 0xFFFFC0000000000
|
||||
|
||||
static const SubGhzBlockConst ws_protocol_ambient_weather_const = {
|
||||
.te_short = 500,
|
||||
.te_long = 1000,
|
||||
.te_delta = 120,
|
||||
.min_count_bit_for_found = 48,
|
||||
};
|
||||
|
||||
struct WSProtocolDecoderAmbient_Weather {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
WSBlockGeneric generic;
|
||||
ManchesterState manchester_saved_state;
|
||||
uint16_t header_count;
|
||||
};
|
||||
|
||||
struct WSProtocolEncoderAmbient_Weather {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
WSBlockGeneric generic;
|
||||
};
|
||||
|
||||
const SubGhzProtocolDecoder ws_protocol_ambient_weather_decoder = {
|
||||
.alloc = ws_protocol_decoder_ambient_weather_alloc,
|
||||
.free = ws_protocol_decoder_ambient_weather_free,
|
||||
|
||||
.feed = ws_protocol_decoder_ambient_weather_feed,
|
||||
.reset = ws_protocol_decoder_ambient_weather_reset,
|
||||
|
||||
.get_hash_data = ws_protocol_decoder_ambient_weather_get_hash_data,
|
||||
.serialize = ws_protocol_decoder_ambient_weather_serialize,
|
||||
.deserialize = ws_protocol_decoder_ambient_weather_deserialize,
|
||||
.get_string = ws_protocol_decoder_ambient_weather_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder ws_protocol_ambient_weather_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol ws_protocol_ambient_weather = {
|
||||
.name = WS_PROTOCOL_AMBIENT_WEATHER_NAME,
|
||||
.type = SubGhzProtocolWeatherStation,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 |
|
||||
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable,
|
||||
|
||||
.decoder = &ws_protocol_ambient_weather_decoder,
|
||||
.encoder = &ws_protocol_ambient_weather_encoder,
|
||||
};
|
||||
|
||||
void* ws_protocol_decoder_ambient_weather_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
WSProtocolDecoderAmbient_Weather* instance = malloc(sizeof(WSProtocolDecoderAmbient_Weather));
|
||||
instance->base.protocol = &ws_protocol_ambient_weather;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_ambient_weather_free(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderAmbient_Weather* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_ambient_weather_reset(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderAmbient_Weather* instance = context;
|
||||
manchester_advance(
|
||||
instance->manchester_saved_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_saved_state,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static bool ws_protocol_ambient_weather_check_crc(WSProtocolDecoderAmbient_Weather* instance) {
|
||||
uint8_t msg[] = {
|
||||
instance->decoder.decode_data >> 40,
|
||||
instance->decoder.decode_data >> 32,
|
||||
instance->decoder.decode_data >> 24,
|
||||
instance->decoder.decode_data >> 16,
|
||||
instance->decoder.decode_data >> 8};
|
||||
|
||||
uint8_t crc = subghz_protocol_blocks_lfsr_digest8(msg, 5, 0x98, 0x3e) ^ 0x64;
|
||||
return (crc == (uint8_t)(instance->decoder.decode_data & 0xFF));
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a WSBlockGeneric* instance
|
||||
*/
|
||||
static void ws_protocol_ambient_weather_remote_controller(WSBlockGeneric* instance) {
|
||||
instance->id = (instance->data >> 32) & 0xFF;
|
||||
instance->battery_low = (instance->data >> 31) & 1;
|
||||
instance->channel = ((instance->data >> 28) & 0x07) + 1;
|
||||
instance->temp = ws_block_generic_fahrenheit_to_celsius(
|
||||
((float)((instance->data >> 16) & 0x0FFF) - 400.0f) / 10.0f);
|
||||
instance->humidity = (instance->data >> 8) & 0xFF;
|
||||
instance->btn = WS_NO_BTN;
|
||||
|
||||
// ToDo maybe it won't be needed
|
||||
/*
|
||||
Sanity checks to reduce false positives and other bad data
|
||||
Packets with Bad data often pass the MIC check.
|
||||
- humidity > 100 (such as 255) and
|
||||
- temperatures > 140 F (such as 369.5 F and 348.8 F
|
||||
Specs in the F007TH and F012TH manuals state the range is:
|
||||
- Temperature: -40 to 140 F
|
||||
- Humidity: 10 to 99%
|
||||
@todo - sanity check b[0] "model number"
|
||||
- 0x45 - F007TH and F012TH
|
||||
- 0x?5 - SwitchDocLabs F016TH temperature sensor (based on comment b[0] & 0x0f == 5)
|
||||
- ? - TFA 30.3208.02
|
||||
if (instance->humidity < 0 || instance->humidity > 100) {
|
||||
ERROR;
|
||||
}
|
||||
|
||||
if (instance->temp < -40.0 || instance->temp > 140.0) {
|
||||
ERROR;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
void ws_protocol_decoder_ambient_weather_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderAmbient_Weather* instance = context;
|
||||
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
if(!level) {
|
||||
if(DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_short) <
|
||||
ws_protocol_ambient_weather_const.te_delta) {
|
||||
event = ManchesterEventShortLow;
|
||||
} else if(
|
||||
DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_long) <
|
||||
ws_protocol_ambient_weather_const.te_delta * 2) {
|
||||
event = ManchesterEventLongLow;
|
||||
}
|
||||
} else {
|
||||
if(DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_short) <
|
||||
ws_protocol_ambient_weather_const.te_delta) {
|
||||
event = ManchesterEventShortHigh;
|
||||
} else if(
|
||||
DURATION_DIFF(duration, ws_protocol_ambient_weather_const.te_long) <
|
||||
ws_protocol_ambient_weather_const.te_delta * 2) {
|
||||
event = ManchesterEventLongHigh;
|
||||
}
|
||||
}
|
||||
if(event != ManchesterEventReset) {
|
||||
bool data;
|
||||
bool data_ok = manchester_advance(
|
||||
instance->manchester_saved_state, event, &instance->manchester_saved_state, &data);
|
||||
|
||||
if(data_ok) {
|
||||
instance->decoder.decode_data = (instance->decoder.decode_data << 1) | !data;
|
||||
}
|
||||
|
||||
if(((instance->decoder.decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) ==
|
||||
AMBIENT_WEATHER_PACKET_HEADER_1) ||
|
||||
((instance->decoder.decode_data & AMBIENT_WEATHER_PACKET_HEADER_MASK) ==
|
||||
AMBIENT_WEATHER_PACKET_HEADER_2)) {
|
||||
if(ws_protocol_ambient_weather_check_crc(instance)) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit =
|
||||
ws_protocol_ambient_weather_const.min_count_bit_for_found;
|
||||
ws_protocol_ambient_weather_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 {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_saved_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_saved_state,
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t ws_protocol_decoder_ambient_weather_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderAmbient_Weather* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
bool ws_protocol_decoder_ambient_weather_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderAmbient_Weather* instance = context;
|
||||
return ws_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
bool ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderAmbient_Weather* instance = context;
|
||||
bool ret = false;
|
||||
do {
|
||||
if(!ws_block_generic_deserialize(&instance->generic, flipper_format)) {
|
||||
break;
|
||||
}
|
||||
if(instance->generic.data_count_bit !=
|
||||
ws_protocol_ambient_weather_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_ambient_weather_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
WSProtocolDecoderAmbient_Weather* 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);
|
||||
}
|
||||
@@ -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_AMBIENT_WEATHER_NAME "Ambient_Weather"
|
||||
|
||||
typedef struct WSProtocolDecoderAmbient_Weather WSProtocolDecoderAmbient_Weather;
|
||||
typedef struct WSProtocolEncoderAmbient_Weather WSProtocolEncoderAmbient_Weather;
|
||||
|
||||
extern const SubGhzProtocolDecoder ws_protocol_ambient_weather_decoder;
|
||||
extern const SubGhzProtocolEncoder ws_protocol_ambient_weather_encoder;
|
||||
extern const SubGhzProtocol ws_protocol_ambient_weather;
|
||||
|
||||
/**
|
||||
* Allocate WSProtocolDecoderAmbient_Weather.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return WSProtocolDecoderAmbient_Weather* pointer to a WSProtocolDecoderAmbient_Weather instance
|
||||
*/
|
||||
void* ws_protocol_decoder_ambient_weather_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free WSProtocolDecoderAmbient_Weather.
|
||||
* @param context Pointer to a WSProtocolDecoderAmbient_Weather instance
|
||||
*/
|
||||
void ws_protocol_decoder_ambient_weather_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder WSProtocolDecoderAmbient_Weather.
|
||||
* @param context Pointer to a WSProtocolDecoderAmbient_Weather instance
|
||||
*/
|
||||
void ws_protocol_decoder_ambient_weather_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a WSProtocolDecoderAmbient_Weather instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void ws_protocol_decoder_ambient_weather_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a WSProtocolDecoderAmbient_Weather instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t ws_protocol_decoder_ambient_weather_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data WSProtocolDecoderAmbient_Weather.
|
||||
* @param context Pointer to a WSProtocolDecoderAmbient_Weather 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_ambient_weather_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data WSProtocolDecoderAmbient_Weather.
|
||||
* @param context Pointer to a WSProtocolDecoderAmbient_Weather instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return true On success
|
||||
*/
|
||||
bool ws_protocol_decoder_ambient_weather_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a WSProtocolDecoderAmbient_Weather instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void ws_protocol_decoder_ambient_weather_get_string(void* context, FuriString* output);
|
||||