Initial Wiegand application
This commit is contained in:
parent
e9b37d5b50
commit
25caf3752c
29
gpio/wiegand/README.md
Normal file
29
gpio/wiegand/README.md
Normal 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.
|
10
gpio/wiegand/application.fam
Normal file
10
gpio/wiegand/application.fam
Normal 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",
|
||||||
|
)
|
184
gpio/wiegand/scenes/wiegand_data.c
Normal file
184
gpio/wiegand/scenes/wiegand_data.c
Normal 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;
|
||||||
|
}
|
27
gpio/wiegand/scenes/wiegand_instructions.c
Normal file
27
gpio/wiegand/scenes/wiegand_instructions.c
Normal 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);
|
||||||
|
}
|
75
gpio/wiegand/scenes/wiegand_load.c
Normal file
75
gpio/wiegand/scenes/wiegand_load.c
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
66
gpio/wiegand/scenes/wiegand_main_menu.c
Normal file
66
gpio/wiegand/scenes/wiegand_main_menu.c
Normal 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;
|
||||||
|
}
|
50
gpio/wiegand/scenes/wiegand_play.c
Normal file
50
gpio/wiegand/scenes/wiegand_play.c
Normal 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);
|
||||||
|
}
|
99
gpio/wiegand/scenes/wiegand_read.c
Normal file
99
gpio/wiegand/scenes/wiegand_read.c
Normal 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);
|
||||||
|
}
|
105
gpio/wiegand/scenes/wiegand_save.c
Normal file
105
gpio/wiegand/scenes/wiegand_save.c
Normal 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
114
gpio/wiegand/wiegand.c
Normal 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
125
gpio/wiegand/wiegand.h
Normal 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
BIN
gpio/wiegand/wiegand.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
Loading…
Reference in New Issue
Block a user