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

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;
}