Initial Wiegand application

This commit is contained in:
Derek Jamison 2023-04-06 23:46:18 -04:00
parent e9b37d5b50
commit 25caf3752c
12 changed files with 884 additions and 0 deletions

29
gpio/wiegand/README.md Normal file
View File

@ -0,0 +1,29 @@
# Wiegand
This application supports W4, W8, W24, W26, W32, W34, W37 and W40 formats.
This application can be used to test Wiegand readers and keypads. It can
save the data to a file, and can load and replay the data. Timings are
measured and displayed; which can be used to help debug Wiegand readers.
## W4: 4-bit Wiegand
This format is used by some keypads. Digits 0-9 are sent as 0-9. ESC is sent as 10 and ENTER as 11. There is no parity bit. The application will display
the button pressed (including ESC and ENTER).
## W8: 8-bit Wiegand
This format is used by some keypads. The last 4 bits are the actual data.Digits 0-9 are sent as 0-9. ESC is sent as 10 and ENTER as 11. The first 4 bits are the inverse of the last 4 bits. The application will display
the button pressed (including ESC and ENTER). If there are any bit errors, the application will show the incorrect bits.
## W26: 26-bit Wiegand
This is a 26-bit format used by many readers. The first bit is an even parity bit. The next 8 bits are the facility code. The next 16 bits are the card number. The last bit is an odd parity bit. The application will display the facility code and card number. If there are any bit errors, the application will show the incorrect bits (the even partity is the first 13 bits, odd parity is the last 13 bits).
## W24: 24-bit Wiegand
This is similar to W26, but without the leading and trailing parity bits. The first 8 bits are the facility code. The next 16 bits are the card number. The application will display the facility code and card number.
## W32/W34/W37/W40: 32/34/37/40-bit Wiegand
These formats are not very standardized, so the application will not try to interpret the data. You can modify the wiegand_data.c file to add your own interpretation.

View File

@ -0,0 +1,10 @@
App(
appid="Wiegand_Reader",
name="Wiegand Reader",
apptype=FlipperAppType.EXTERNAL,
entry_point="wiegand_app",
requires=["gui"],
stack_size=2 * 1024,
fap_icon="wiegand.png",
fap_category="GPIO",
)

View File

@ -0,0 +1,184 @@
#include "..\wiegand.h"
void wiegand_add_info_4bit_8bit(FuriString* buffer) {
if(bit_count == 8) {
for(int i = 0; i < 4; i++) {
furi_string_cat_printf(
buffer, "\nbit %d: %d %d (bit %d)", i, data[i], data[i + 4], i + 4);
if(data[i] == data[i + 4]) {
furi_string_cat_printf(buffer, " - ERROR");
} else {
furi_string_cat_printf(buffer, " - OK");
}
}
}
if(bit_count == 4 || bit_count == 8) {
int code = 0;
int offset = bit_count == 4 ? 0 : 4;
for(int i = 0; i < 4; i++) {
code = code << 1;
code += data[i + offset] ? 1 : 0;
}
if(code <= 9) {
furi_string_cat_printf(buffer, "\nButton: %d", code);
} else if(code == 10) {
furi_string_cat_printf(buffer, "\nButton: Escape");
} else if(code == 11) {
furi_string_cat_printf(buffer, "\nButton: Enter");
}
}
}
void wiegand_add_info_26bit(FuriString* buffer) {
// 26 bit wiegand, the first bit is the even parity bit, which is
// based on the next 12 bits. The number of bits that are a 1 should
// be even.
int parity = 0;
if(data[0]) {
parity = 1;
}
for(int i = 1; i < 13; i++) {
if(data[i]) {
parity++;
}
}
if(parity % 2 == 0) {
furi_string_cat_printf(buffer, "\nEven Parity (%d): OK", parity);
} else {
furi_string_cat_printf(buffer, "\nEven Parity (%d): ERROR", parity);
}
// After the parity bit, the next 8 bits are the facility code.
// Then the next 16 bits are the card id .
furi_string_cat_printf(buffer, "\nFacility: 0x");
int code = 0;
int count = 0;
for(int i = 1; i < 25; i++) {
code = code << 1;
code |= data[i] ? 1 : 0;
if(++count % 4 == 0) {
furi_string_cat_printf(buffer, "%X", code);
code = 0;
}
// Parity, then 8 bit facility code, then id.
if(i == 9) {
furi_string_cat_printf(buffer, "\nId: 0x");
}
}
if(data[13]) {
parity = 1;
} else {
parity = 0;
}
for(int i = 14; i < 26; i++) {
if(data[i]) {
parity++;
}
}
if(parity % 2 == 0) {
furi_string_cat_printf(buffer, "\nOdd Parity (%d): ERROR", parity);
} else {
furi_string_cat_printf(buffer, "\nOdd Parity (%d): OK", parity);
}
}
void wiegand_add_info_24bit(FuriString* buffer) {
// 24 bit wiegand (no parity info).
// The First 8 bits are the facility code.
// Then the next 16 bits are the card id.
furi_string_cat_printf(buffer, "\nFacility: 0x");
int code = 0;
int count = 0;
for(int i = 0; i < 24; i++) {
code = code << 1;
code |= data[i] ? 1 : 0;
if(++count % 4 == 0) {
furi_string_cat_printf(buffer, "%X", code);
code = 0;
}
// The first 8 bits are facility code, then comes id.
if(i == 8) {
furi_string_cat_printf(buffer, "\nId: 0x");
}
}
}
void wiegand_add_info(FuriString* buffer) {
furi_string_push_back(buffer, '\n');
if(bit_count == 4 || bit_count == 8) {
wiegand_add_info_4bit_8bit(buffer);
} else if(bit_count == 26) {
wiegand_add_info_26bit(buffer);
} else if(bit_count == 24) {
wiegand_add_info_24bit(buffer);
}
furi_string_push_back(buffer, '\n');
}
void wiegand_button_callback(GuiButtonType result, InputType type, void* context) {
App* app = context;
if(type == InputTypeShort && result == GuiButtonTypeLeft) {
view_dispatcher_send_custom_event(app->view_dispatcher, WiegandDataSceneSaveButtonEvent);
} else if(type == InputTypeShort && result == GuiButtonTypeCenter) {
view_dispatcher_send_custom_event(app->view_dispatcher, WiegandDataScenePlayButtonEvent);
}
}
void wiegand_data_scene_on_enter(void* context) {
App* app = context;
widget_reset(app->widget);
widget_add_string_element(app->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Wiegand Data");
FuriString* buffer = furi_string_alloc(1024);
furi_string_printf(buffer, "Bits: %d\n", bit_count);
for(int i = 0; i < bit_count; i++) {
furi_string_push_back(buffer, data[i] ? '1' : '0');
if((bit_count - i - 1) % 22 == 21) {
furi_string_push_back(buffer, '\n');
}
}
furi_string_cat_printf(buffer, "\nPulse: %ld us", (data_rise[0] - data_fall[0]) / 64);
furi_string_cat_printf(buffer, "\nPeriod: %ld us", (data_fall[1] - data_fall[0]) / 64);
wiegand_add_info(buffer);
for(int i = 0; i < bit_count;) {
uint32_t pulse = (data_rise[i] - data_fall[i]) / 64;
i++;
uint32_t period = (i < bit_count) ? (data_fall[i] - data_fall[i - 1]) / 64 : 0;
furi_string_cat_printf(
buffer, "\n%c : %ld us, %ld us", data[i] ? '1' : '0', pulse, period);
}
widget_add_text_scroll_element(app->widget, 0, 12, 128, 34, furi_string_get_cstr(buffer));
if(!data_saved) {
widget_add_button_element(
app->widget, GuiButtonTypeLeft, "Save", wiegand_button_callback, app);
}
widget_add_button_element(
app->widget, GuiButtonTypeCenter, "Play", wiegand_button_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, WiegandWidgetView);
}
bool wiegand_data_scene_on_event(void* context, SceneManagerEvent event) {
App* app = context;
bool consumed = false;
switch(event.type) {
case SceneManagerEventTypeCustom:
switch(event.event) {
case WiegandDataScenePlayButtonEvent:
wiegand_play();
consumed = true;
break;
case WiegandDataSceneSaveButtonEvent:
scene_manager_next_scene(app->scene_manager, WiegandSaveScene);
consumed = true;
break;
default:
consumed = false;
break;
}
break;
default:
break;
}
return consumed;
}

View File

@ -0,0 +1,27 @@
#include "..\wiegand.h"
void wiegand_instructions_scene_on_enter(void* context) {
App* app = context;
widget_reset(app->widget);
widget_add_string_element(app->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Instructions");
widget_add_text_scroll_element(
app->widget,
0,
15,
128,
40,
"Only use on devices you own!\n"
"Connect D0 (Green) to pin A4\n"
"Connect D1 (White) to pin A7\n"
"Connect GND (Black) to GND\n"
"Add a 10K inline resistor on D0\n"
"between keypad and door.\n"
"Connect Flipper on door\n"
"side. Add 10K inline resistor\n"
"on D1 between keypad and\n"
"door. Connect Flipper on\n"
"door side. Do not inturrupt\n"
"the D0/D1 connections while\n"
"adding the resistors.");
view_dispatcher_switch_to_view(app->view_dispatcher, WiegandWidgetView);
}

View File

@ -0,0 +1,75 @@
#include "..\wiegand.h"
void wiegand_load_scene_on_enter(void* context) {
App* app = context;
bit_count = 0;
DialogsFileBrowserOptions browser_options;
dialog_file_browser_set_basic_options(&browser_options, WIEGAND_SAVE_EXTENSION, NULL);
browser_options.base_path = WIEGAND_SAVE_FOLDER;
furi_string_set(app->file_path, browser_options.base_path);
FuriString* buffer = furi_string_alloc(1024);
if(dialog_file_browser_show(app->dialogs, app->file_path, app->file_path, &browser_options)) {
Storage* storage = furi_record_open(RECORD_STORAGE);
File* data_file = storage_file_alloc(storage);
if(storage_file_open(
data_file, furi_string_get_cstr(app->file_path), FSAM_READ, FSOM_OPEN_EXISTING)) {
while(!storage_file_eof(data_file)) {
char ch;
furi_string_reset(buffer);
while(storage_file_read(data_file, &ch, 1) && !storage_file_eof(data_file)) {
furi_string_push_back(buffer, ch);
if(ch == '\n') {
break;
}
}
if(furi_string_start_with(buffer, "RAW_Data: ")) {
bit_count = 0;
int length = furi_string_size(buffer);
uint32_t temp;
char ch;
for(int i = 8; i < length - 1; i++) {
if(furi_string_get_char(buffer, i) == 'D') {
i++;
data[bit_count] = furi_string_get_char(buffer, i) == '1';
i += 2; // Skip space
temp = 0;
while(i < length && (ch = furi_string_get_char(buffer, i)) != ' ') {
temp = temp * 10 + (ch - '0');
i++;
}
data_fall[bit_count] = temp;
i++; // Skip space
temp = 0;
while(i < length && (ch = furi_string_get_char(buffer, i)) != ' ' &&
ch != '\n') {
temp = temp * 10 + (ch - '0');
i++;
}
data_rise[bit_count] = temp;
bit_count++;
}
}
break;
}
}
storage_file_close(data_file);
}
storage_file_free(data_file);
furi_record_close(RECORD_STORAGE);
}
if(bit_count == 0) {
scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, WiegandMainMenuScene);
} else {
data_saved = true;
scene_manager_next_scene(app->scene_manager, WiegandDataScene);
}
}

View File

@ -0,0 +1,66 @@
#include "..\wiegand.h"
/*
Triggers a custom event that is handled in the main menu on_scene handler.
@param context Pointer to the application context.
@param index Index of the selected menu item to map to custom event.
*/
void wiegand_menu_callback(void* context, uint32_t index) {
App* app = context;
WiegandMainMenuEvent event = WiegandMainMenuUnknownEvent;
switch(index) {
case WiegandMainMenuInstructions:
event = WiegandMainMenuInstructionsEvent;
break;
case WiegandMainMenuRead:
event = WiegandMainMenuReadEvent;
break;
case WiegandMainMenuLoad:
event = WiegandMainMenuLoadEvent;
break;
}
if(event != WiegandMainMenuUnknownEvent) {
scene_manager_handle_custom_event(app->scene_manager, event);
}
}
/*
Displays the main menu.
@param context Pointer to the application context.
*/
void wiegand_main_menu_scene_on_enter(void* context) {
App* app = context;
submenu_reset(app->submenu);
submenu_set_header(app->submenu, "Wiegand");
submenu_add_item(
app->submenu, "Instructions", WiegandMainMenuInstructions, wiegand_menu_callback, app);
submenu_add_item(app->submenu, "Read", WiegandMainMenuRead, wiegand_menu_callback, app);
submenu_add_item(app->submenu, "Load", WiegandMainMenuLoad, wiegand_menu_callback, app);
view_dispatcher_switch_to_view(app->view_dispatcher, WiegandSubmenuView);
}
bool wiegand_main_menu_scene_on_event(void* context, SceneManagerEvent event) {
App* app = context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case WiegandMainMenuInstructionsEvent:
scene_manager_next_scene(app->scene_manager, WiegandInstructionsScene);
consumed = true;
break;
case WiegandMainMenuReadEvent:
scene_manager_next_scene(app->scene_manager, WiegandReadScene);
consumed = true;
break;
case WiegandMainMenuLoadEvent:
scene_manager_next_scene(app->scene_manager, WiegandLoadScene);
consumed = true;
break;
default:
consumed = false;
break;
}
}
return consumed;
}

View File

@ -0,0 +1,50 @@
#include "..\wiegand.h"
void single_vibro() {
NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
notification_message(notification, &sequence_single_vibro);
furi_record_close(RECORD_NOTIFICATION);
}
void wiegand_play() {
uint32_t* delays = malloc(sizeof(uint32_t) * bit_count * 2);
for(int i = 0; i < bit_count - 1; i++) {
delays[i * 2] = (data_rise[i] - data_fall[i]) / 64;
delays[i * 2 + 1] = (data_fall[i + 1] - data_rise[i]) / 64;
}
delays[(bit_count - 1) * 2] = (data_rise[bit_count - 1] - data_fall[bit_count - 1]) / 64;
delays[(bit_count - 1) * 2 + 1] = 1;
for(int i = 0; i < bit_count; i++) {
// Delays are always at least 1 tick.
if(delays[i * 2] == 0) delays[i * 2] = 1;
if(delays[i * 2 + 1] == 0) delays[i * 2 + 1] = 1;
if(delays[i * 2 + 1] > 5) delays[i * 2 + 1] -= 1;
}
furi_hal_gpio_write(pinD0, true);
furi_hal_gpio_init(pinD0, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pinD1, true);
furi_hal_gpio_init(pinD1, GpioModeOutputOpenDrain, GpioPullNo, GpioSpeedVeryHigh);
single_vibro();
furi_delay_ms(500);
int j = 0;
for(int i = 0; i < bit_count; i++) {
if(data[i]) {
furi_hal_gpio_write(pinD1, false);
furi_delay_us(delays[j++]);
furi_hal_gpio_write(pinD1, true);
furi_delay_us(delays[j++]);
} else {
furi_hal_gpio_write(pinD0, false);
furi_delay_us(delays[j++]);
furi_hal_gpio_write(pinD0, true);
furi_delay_us(delays[j++]);
}
}
furi_hal_gpio_init_simple(pinD0, GpioModeAnalog);
furi_hal_gpio_init_simple(pinD1, GpioModeAnalog);
}

View File

@ -0,0 +1,99 @@
#include "..\wiegand.h"
void wiegand_isr_d0(void* context) {
UNUSED(context);
uint32_t time = DWT->CYCCNT;
bool rise = furi_hal_gpio_read(pinD0);
data[bit_count] = 0;
if(rise) {
data_rise[bit_count] = time;
if(bit_count < MAX_BITS) {
bit_count++;
}
} else {
data_fall[bit_count] = time;
}
}
void wiegand_isr_d1(void* context) {
UNUSED(context);
uint32_t time = DWT->CYCCNT;
bool rise = furi_hal_gpio_read(pinD1);
data[bit_count] = 1;
if(rise) {
data_rise[bit_count] = time;
if(bit_count < MAX_BITS) {
bit_count++;
}
} else {
data_fall[bit_count] = time;
}
}
void wiegand_start_read(void* context) {
App* app = context;
data_saved = false;
bit_count = 0;
furi_hal_gpio_init_simple(pinD0, GpioModeInterruptRiseFall);
furi_hal_gpio_init_simple(pinD1, GpioModeInterruptRiseFall);
furi_hal_gpio_add_int_callback(pinD0, wiegand_isr_d0, NULL);
furi_hal_gpio_add_int_callback(pinD1, wiegand_isr_d1, NULL);
furi_timer_start(app->timer, 100);
}
void wiegand_stop_read(void* context) {
App* app = context;
furi_hal_gpio_remove_int_callback(pinD0);
furi_hal_gpio_remove_int_callback(pinD1);
furi_hal_gpio_init_simple(pinD0, GpioModeAnalog);
furi_hal_gpio_init_simple(pinD1, GpioModeAnalog);
furi_timer_stop(app->timer);
}
void wiegand_timer_callback(void* context) {
App* app = context;
uint32_t duration = DWT->CYCCNT;
const uint32_t one_millisecond = 64000;
if(bit_count == 0) {
return;
}
duration -= data_fall[bit_count - 1];
FURI_CRITICAL_ENTER();
if(duration > 25 * one_millisecond) {
if(bit_count == 4 || bit_count == 8 || bit_count == 24 || bit_count == 26 ||
bit_count == 32 || bit_count == 34 || bit_count == 37 || bit_count == 40) {
wiegand_stop_read(app);
scene_manager_next_scene(app->scene_manager, WiegandDataScene);
} else {
// No data, clear
bit_count = 0;
}
}
FURI_CRITICAL_EXIT();
}
void wiegand_read_scene_on_enter(void* context) {
App* app = context;
widget_reset(app->widget);
widget_add_string_element(app->widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Read Wiegand");
widget_add_string_element(
app->widget, 0, 25, AlignLeft, AlignTop, FontSecondary, "Waiting for signal...");
widget_add_string_element(
app->widget, 0, 45, AlignLeft, AlignTop, FontSecondary, "D0/Green/A4");
widget_add_string_element(
app->widget, 0, 53, AlignLeft, AlignTop, FontSecondary, "D1/White/A7");
wiegand_start_read(app);
view_dispatcher_switch_to_view(app->view_dispatcher, WiegandWidgetView);
}
void wiegand_read_scene_on_exit(void* context) {
App* app = context;
wiegand_stop_read(app);
}

View File

@ -0,0 +1,105 @@
#include "..\wiegand.h"
void wiegand_data_scene_save_name_text_input_callback(void* context) {
App* app = context;
view_dispatcher_send_custom_event(app->view_dispatcher, WiegandDataSceneSaveFileEvent);
}
void ensure_dir_exists(Storage* storage) {
// If apps_data directory doesn't exist, create it.
if(!storage_dir_exists(storage, WIEGAND_APPS_DATA_FOLDER)) {
FURI_LOG_I(TAG, "Creating directory: %s", WIEGAND_APPS_DATA_FOLDER);
storage_simply_mkdir(storage, WIEGAND_APPS_DATA_FOLDER);
} else {
FURI_LOG_I(TAG, "Directory exists: %s", WIEGAND_APPS_DATA_FOLDER);
}
// If wiegand directory doesn't exist, create it.
if(!storage_dir_exists(storage, WIEGAND_SAVE_FOLDER)) {
FURI_LOG_I(TAG, "Creating directory: %s", WIEGAND_SAVE_FOLDER);
storage_simply_mkdir(storage, WIEGAND_SAVE_FOLDER);
} else {
FURI_LOG_I(TAG, "Directory exists: %s", WIEGAND_SAVE_FOLDER);
}
}
void wiegand_save(void* context) {
App* app = context;
FuriString* buffer = furi_string_alloc(1024);
FuriString* file_path = furi_string_alloc();
furi_string_printf(
file_path, "%s/%s%s", WIEGAND_SAVE_FOLDER, app->file_name, WIEGAND_SAVE_EXTENSION);
Storage* storage = furi_record_open(RECORD_STORAGE);
ensure_dir_exists(storage);
File* data_file = storage_file_alloc(storage);
if(storage_file_open(
data_file, furi_string_get_cstr(file_path), FSAM_WRITE, FSOM_OPEN_ALWAYS)) {
furi_string_printf(buffer, "Filetype: Flipper Wiegand Key File\n");
storage_file_write(data_file, furi_string_get_cstr(buffer), furi_string_size(buffer));
furi_string_printf(buffer, "Version: 1\n");
storage_file_write(data_file, furi_string_get_cstr(buffer), furi_string_size(buffer));
furi_string_printf(buffer, "Protocol: RAW\n");
storage_file_write(data_file, furi_string_get_cstr(buffer), furi_string_size(buffer));
furi_string_printf(buffer, "Bits: %d\n", bit_count);
storage_file_write(data_file, furi_string_get_cstr(buffer), furi_string_size(buffer));
furi_string_printf(buffer, "RAW_Data: ");
for(int i = 0; i < bit_count; i++) {
furi_string_cat_printf(
buffer,
"D%d %ld %ld ",
data[i] ? 1 : 0,
data_fall[i] - data_fall[0],
data_rise[i] - data_fall[0]);
}
furi_string_push_back(buffer, '\n');
storage_file_write(data_file, furi_string_get_cstr(buffer), furi_string_size(buffer));
storage_file_close(data_file);
}
storage_file_free(data_file);
furi_record_close(RECORD_STORAGE);
furi_string_free(file_path);
furi_string_free(buffer);
}
void wiegand_save_scene_on_enter(void* context) {
App* app = context;
text_input_reset(app->text_input);
set_random_name(app->file_name, WIEGAND_KEY_NAME_SIZE);
text_input_set_header_text(app->text_input, "Name the key");
text_input_set_result_callback(
app->text_input,
wiegand_data_scene_save_name_text_input_callback,
app,
app->file_name,
WIEGAND_KEY_NAME_SIZE,
true);
view_dispatcher_switch_to_view(app->view_dispatcher, WiegandTextInputView);
}
bool wiegand_save_scene_on_event(void* context, SceneManagerEvent event) {
App* app = context;
bool consumed = false;
switch(event.type) {
case SceneManagerEventTypeCustom:
switch(event.event) {
case WiegandDataSceneSaveFileEvent:
wiegand_save(app);
data_saved = true;
scene_manager_search_and_switch_to_previous_scene(
app->scene_manager, WiegandDataScene);
consumed = true;
break;
default:
consumed = false;
break;
}
break;
default:
break;
}
return consumed;
}

114
gpio/wiegand/wiegand.c Normal file
View File

@ -0,0 +1,114 @@
#include "wiegand.h"
const GpioPin* const pinD0 = &gpio_ext_pa4;
const GpioPin* const pinD1 = &gpio_ext_pa7;
volatile int bit_count = 0;
volatile bool data[MAX_BITS];
volatile uint32_t data_fall[MAX_BITS];
volatile uint32_t data_rise[MAX_BITS];
bool data_saved = false;
bool wiegand_empty_scene_on_event(void* _ctx, SceneManagerEvent _evt) {
UNUSED(_ctx);
UNUSED(_evt);
return false;
}
void wiegand_empty_scene_on_exit(void* _ctx) {
UNUSED(_ctx);
}
void (*const basic_scenes_scene_on_enter_handlers[])(void*) = {
wiegand_main_menu_scene_on_enter,
wiegand_instructions_scene_on_enter,
wiegand_read_scene_on_enter,
wiegand_data_scene_on_enter,
wiegand_save_scene_on_enter,
wiegand_load_scene_on_enter,
};
bool (*const basic_scenes_scene_on_event_handlers[])(void*, SceneManagerEvent) = {
wiegand_main_menu_scene_on_event,
wiegand_empty_scene_on_event, // instructions
wiegand_empty_scene_on_event, // read
wiegand_data_scene_on_event,
wiegand_save_scene_on_event,
wiegand_empty_scene_on_event, // load
};
void (*const basic_scenes_scene_on_exit_handlers[])(void*) = {
wiegand_empty_scene_on_exit, // main_menu
wiegand_empty_scene_on_exit, // instructions
wiegand_read_scene_on_exit,
wiegand_empty_scene_on_exit, // data
wiegand_empty_scene_on_exit, // save
wiegand_empty_scene_on_exit, // load
};
const SceneManagerHandlers basic_scenes_scene_manager_handlers = {
.on_enter_handlers = basic_scenes_scene_on_enter_handlers,
.on_event_handlers = basic_scenes_scene_on_event_handlers,
.on_exit_handlers = basic_scenes_scene_on_exit_handlers,
.scene_num = WiegandSceneCount,
};
bool wiegand_custom_callback(void* context, uint32_t custom_event) {
furi_assert(context);
App* app = context;
return scene_manager_handle_custom_event(app->scene_manager, custom_event);
}
bool wiegand_back_event_callback(void* context) {
furi_assert(context);
App* app = context;
return scene_manager_handle_back_event(app->scene_manager);
}
App* app_alloc() {
App* app = malloc(sizeof(App));
app->scene_manager = scene_manager_alloc(&basic_scenes_scene_manager_handlers, app);
app->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_enable_queue(app->view_dispatcher);
view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_set_custom_event_callback(app->view_dispatcher, wiegand_custom_callback);
view_dispatcher_set_navigation_event_callback(
app->view_dispatcher, wiegand_back_event_callback);
app->submenu = submenu_alloc();
view_dispatcher_add_view(
app->view_dispatcher, WiegandSubmenuView, submenu_get_view(app->submenu));
app->widget = widget_alloc();
view_dispatcher_add_view(
app->view_dispatcher, WiegandWidgetView, widget_get_view(app->widget));
app->text_input = text_input_alloc();
view_dispatcher_add_view(
app->view_dispatcher, WiegandTextInputView, text_input_get_view(app->text_input));
app->dialogs = furi_record_open(RECORD_DIALOGS);
app->file_path = furi_string_alloc();
app->timer = furi_timer_alloc(wiegand_timer_callback, FuriTimerTypePeriodic, app);
return app;
}
void app_free(void* context) {
App* app = context;
furi_assert(app);
scene_manager_free(app->scene_manager);
view_dispatcher_free(app->view_dispatcher);
submenu_free(app->submenu);
widget_free(app->widget);
text_input_free(app->text_input);
furi_timer_free(app->timer);
furi_record_close(RECORD_DIALOGS);
free(app);
}
int wiegand_app(void* p) {
UNUSED(p);
App* app = app_alloc();
Gui* gui = furi_record_open(RECORD_GUI);
view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
scene_manager_next_scene(app->scene_manager, WiegandMainMenuScene);
view_dispatcher_run(app->view_dispatcher);
app_free(app);
return 0;
}

125
gpio/wiegand/wiegand.h Normal file
View File

@ -0,0 +1,125 @@
#pragma once
#include <furi.h>
#include <gui/gui.h>
#include <gui/view_dispatcher.h>
#include <gui/scene_manager.h>
#include <gui/modules/widget.h>
#include <gui/modules/submenu.h>
#include <gui/modules/text_input.h>
#include <lib/toolbox/random_name.h>
#include <notification/notification.h>
#include <notification/notification_messages.h>
#include <dialogs/dialogs.h>
#include <storage/storage.h>
extern const GpioPin* const pinD0;
extern const GpioPin* const pinD1;
extern volatile int bit_count;
#define MAX_BITS 42
extern volatile bool data[];
extern volatile uint32_t data_fall[];
extern volatile uint32_t data_rise[];
extern bool data_saved;
#define WIEGAND_KEY_NAME_SIZE 25
#define WIEGAND_APPS_DATA_FOLDER EXT_PATH("apps_data")
#define WIEGAND_SAVE_FOLDER \
WIEGAND_APPS_DATA_FOLDER "/" \
"wiegand"
#define WIEGAND_SAVE_EXTENSION ".wgn"
#define TAG "WIEGAND_APP"
typedef enum {
WiegandMainMenuScene,
WiegandInstructionsScene,
WiegandReadScene,
WiegandDataScene,
WiegandSaveScene,
WiegandLoadScene,
WiegandSceneCount,
} WiegandScene;
typedef enum {
WiegandSubmenuView,
WiegandWidgetView,
WiegandTextInputView,
} WiegandView;
typedef struct App {
SceneManager* scene_manager;
ViewDispatcher* view_dispatcher;
Submenu* submenu;
Widget* widget;
TextInput* text_input;
DialogsApp* dialogs;
FuriString* file_path;
char file_name[WIEGAND_KEY_NAME_SIZE];
FuriTimer* timer;
} App;
typedef enum {
WiegandMainMenuInstructions,
WiegandMainMenuRead,
WiegandMainMenuLoad,
} WiegandMainMenuSceneIndex;
typedef enum {
WiegandMainMenuUnknownEvent,
WiegandMainMenuInstructionsEvent,
WiegandMainMenuReadEvent,
WiegandMainMenuLoadEvent,
} WiegandMainMenuEvent;
typedef enum {
WiegandDataSceneSaveButtonEvent,
WiegandDataScenePlayButtonEvent,
WiegandDataSceneSaveFileEvent,
} WiegandDataEvent;
void wiegand_menu_callback(void* context, uint32_t index);
void wiegand_main_menu_scene_on_enter(void* context);
bool wiegand_main_menu_scene_on_event(void* context, SceneManagerEvent event);
// void wiegand_main_menu_scene_on_exit(void* context);
void wiegand_instructions_scene_on_enter(void* context);
// bool wiegand_instructions_scene_on_event(void* context, SceneManagerEvent event);
// void wiegand_instructions_scene_on_exit(void* context);
void wiegand_isr_d0(void* context);
void wiegand_isr_d1(void* context);
void wiegand_start_read(void* context);
void wiegand_stop_read(void* context);
void wiegand_timer_callback(void* context);
void wiegand_read_scene_on_enter(void* context);
// bool wiegand_read_scene_on_event(void* context, SceneManagerEvent event);
void wiegand_read_scene_on_exit(void* context);
void wiegand_add_info_4bit_8bit(FuriString* buffer);
void wiegand_add_info_26bit(FuriString* buffer);
void wiegand_add_info_24bit(FuriString* buffer);
void wiegand_add_info(FuriString* buffer);
void single_vibro();
void wiegand_play();
void wiegand_button_callback(GuiButtonType result, InputType type, void* context);
void wiegand_data_scene_on_enter(void* context);
bool wiegand_data_scene_on_event(void* context, SceneManagerEvent event);
// void wiegand_data_scene_on_exit(void* context);
void wiegand_data_scene_save_name_text_input_callback(void* context);
void ensure_dir_exists(Storage* storage);
void wiegand_save(void* context);
void wiegand_save_scene_on_enter(void* context);
bool wiegand_save_scene_on_event(void* context, SceneManagerEvent event);
// void wiegand_save_scene_on_exit(void* context);
void wiegand_load_scene_on_enter(void* context);
// bool wiegand_load_scene_on_event(void* context, SceneManagerEvent event);
// void wiegand_load_scene_on_exit(void* context);
bool wiegand_custom_callback(void* context, uint32_t custom_event);
bool wiegand_back_event_callback(void* context);
// Methods with no implementation.
bool wiegand_empty_scene_on_event(void* context, SceneManagerEvent event);
void wiegand_empty_scene_on_exit(void* _ctx);
App* app_alloc();
void app_free(void* context);

BIN
gpio/wiegand/wiegand.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB