Explorar o código

Add meal_pager from https://github.com/leedave/flipper-zero-meal-pager

git-subtree-dir: meal_pager
git-subtree-mainline: 9658164b62c6bf035eaed17d31a1d188262b7a66
git-subtree-split: 5dcdf9476ebc42badd982507a8e54c6a0fe9f9c4
Willy-JL hai 1 ano
pai
achega
a693d1ff49
Modificáronse 59 ficheiros con 4938 adicións e 0 borrados
  1. 191 0
      meal_pager/.clang-format
  2. 1 0
      meal_pager/.gitsubtree
  3. 66 0
      meal_pager/README.md
  4. 14 0
      meal_pager/application.fam
  5. 9 0
      meal_pager/docs/README.md
  6. 67 0
      meal_pager/docs/changelog.md
  7. 390 0
      meal_pager/helpers/gui/int_input.c
  8. 71 0
      meal_pager/helpers/gui/int_input.h
  9. 80 0
      meal_pager/helpers/meal_pager_calc.c
  10. 12 0
      meal_pager/helpers/meal_pager_calc.h
  11. 67 0
      meal_pager/helpers/meal_pager_custom_event.h
  12. 34 0
      meal_pager/helpers/meal_pager_haptic.c
  13. 10 0
      meal_pager/helpers/meal_pager_haptic.h
  14. 59 0
      meal_pager/helpers/meal_pager_led.c
  15. 13 0
      meal_pager/helpers/meal_pager_led.h
  16. 25 0
      meal_pager/helpers/meal_pager_speaker.c
  17. 8 0
      meal_pager/helpers/meal_pager_speaker.h
  18. 248 0
      meal_pager/helpers/meal_pager_storage.c
  19. 42 0
      meal_pager/helpers/meal_pager_storage.h
  20. 131 0
      meal_pager/helpers/retekess/meal_pager_retekess_t119.c
  21. 8 0
      meal_pager/helpers/retekess/meal_pager_retekess_t119.h
  22. 116 0
      meal_pager/helpers/retekess/meal_pager_retekess_td157.c
  23. 8 0
      meal_pager/helpers/retekess/meal_pager_retekess_td157.h
  24. 118 0
      meal_pager/helpers/retekess/meal_pager_retekess_td165.c
  25. 8 0
      meal_pager/helpers/retekess/meal_pager_retekess_td165.h
  26. 122 0
      meal_pager/helpers/retekess/meal_pager_retekess_td174.c
  27. 7 0
      meal_pager/helpers/retekess/meal_pager_retekess_td174.h
  28. 77 0
      meal_pager/helpers/subghz/subghz.c
  29. 9 0
      meal_pager/helpers/subghz/subghz.h
  30. 14 0
      meal_pager/helpers/subghz/subghz_error_type.h
  31. 260 0
      meal_pager/helpers/subghz/subghz_i.c
  32. 83 0
      meal_pager/helpers/subghz/subghz_i.h
  33. 630 0
      meal_pager/helpers/subghz/subghz_txrx.c
  34. 336 0
      meal_pager/helpers/subghz/subghz_txrx.h
  35. 29 0
      meal_pager/helpers/subghz/subghz_txrx_i.h
  36. 77 0
      meal_pager/helpers/subghz/subghz_types.h
  37. BIN=BIN
      meal_pager/icons/KeyBackspaceSelected_16x9.png
  38. BIN=BIN
      meal_pager/icons/KeyBackspace_16x9.png
  39. BIN=BIN
      meal_pager/icons/KeySaveSelected_24x11.png
  40. BIN=BIN
      meal_pager/icons/KeySave_24x11.png
  41. BIN=BIN
      meal_pager/icons/meal_pager_10px.png
  42. 165 0
      meal_pager/meal_pager.c
  43. 2 0
      meal_pager/meal_pager.h
  44. 104 0
      meal_pager/meal_pager_i.h
  45. 30 0
      meal_pager/scenes/meal_pager_scene.c
  46. 29 0
      meal_pager/scenes/meal_pager_scene.h
  47. 8 0
      meal_pager/scenes/meal_pager_scene_config.h
  48. 131 0
      meal_pager/scenes/meal_pager_scene_menu.c
  49. 67 0
      meal_pager/scenes/meal_pager_scene_set_first_pager.c
  50. 67 0
      meal_pager/scenes/meal_pager_scene_set_first_station.c
  51. 71 0
      meal_pager/scenes/meal_pager_scene_set_last_pager.c
  52. 71 0
      meal_pager/scenes/meal_pager_scene_set_last_station.c
  53. 237 0
      meal_pager/scenes/meal_pager_scene_settings.c
  54. 55 0
      meal_pager/scenes/meal_pager_scene_startscreen.c
  55. 108 0
      meal_pager/scenes/meal_pager_scene_transmit.c
  56. 128 0
      meal_pager/views/meal_pager_startscreen.c
  57. 19 0
      meal_pager/views/meal_pager_startscreen.h
  58. 182 0
      meal_pager/views/meal_pager_transmit.c
  59. 24 0
      meal_pager/views/meal_pager_transmit.h

+ 191 - 0
meal_pager/.clang-format

@@ -0,0 +1,191 @@
+---
+Language:        Cpp
+AccessModifierOffset: -4
+AlignAfterOpenBracket: AlwaysBreak
+AlignArrayOfStructures: None
+AlignConsecutiveMacros: None
+AlignConsecutiveAssignments: None
+AlignConsecutiveBitFields: None
+AlignConsecutiveDeclarations: None
+AlignEscapedNewlines: Left
+AlignOperands:   Align
+AlignTrailingComments: false
+AllowAllArgumentsOnNextLine: true
+AllowAllParametersOfDeclarationOnNextLine: false
+AllowShortEnumsOnASingleLine: true
+AllowShortBlocksOnASingleLine: Never
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortLambdasOnASingleLine: All
+AllowShortIfStatementsOnASingleLine: WithoutElse
+AllowShortLoopsOnASingleLine: true
+AlwaysBreakAfterDefinitionReturnType: None
+AlwaysBreakAfterReturnType: None
+AlwaysBreakBeforeMultilineStrings: false
+AlwaysBreakTemplateDeclarations: Yes
+AttributeMacros:
+  - __capability
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+  AfterCaseLabel:  false
+  AfterClass:      false
+  AfterControlStatement: Never
+  AfterEnum:       false
+  AfterFunction:   false
+  AfterNamespace:  false
+  AfterObjCDeclaration: false
+  AfterStruct:     false
+  AfterUnion:      false
+  AfterExternBlock: false
+  BeforeCatch:     false
+  BeforeElse:      false
+  BeforeLambdaBody: false
+  BeforeWhile:     false
+  IndentBraces:    false
+  SplitEmptyFunction: true
+  SplitEmptyRecord: true
+  SplitEmptyNamespace: true
+BreakBeforeBinaryOperators: None
+BreakBeforeConceptDeclarations: true
+BreakBeforeBraces: Attach
+BreakBeforeInheritanceComma: false
+BreakInheritanceList: BeforeColon
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializersBeforeComma: false
+BreakConstructorInitializers: BeforeComma
+BreakAfterJavaFieldAnnotations: false
+BreakStringLiterals: false
+ColumnLimit:     99
+CommentPragmas:  '^ IWYU pragma:'
+QualifierAlignment: Leave
+CompactNamespaces: false
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: true
+DeriveLineEnding: true
+DerivePointerAlignment: false
+DisableFormat:   false
+EmptyLineAfterAccessModifier: Never
+EmptyLineBeforeAccessModifier: LogicalBlock
+ExperimentalAutoDetectBinPacking: false
+PackConstructorInitializers: BinPack
+BasedOnStyle:    ''
+ConstructorInitializerAllOnOneLineOrOnePerLine: false
+AllowAllConstructorInitializersOnNextLine: true
+FixNamespaceComments: false
+ForEachMacros:
+  - foreach
+  - Q_FOREACH
+  - BOOST_FOREACH
+IfMacros:
+  - KJ_IF_MAYBE
+IncludeBlocks:   Preserve
+IncludeCategories:
+  - Regex:           '.*'
+    Priority:        1
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '^(<|"(gtest|gmock|isl|json)/)'
+    Priority:        3
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '.*'
+    Priority:        1
+    SortPriority:    0
+    CaseSensitive:   false
+IncludeIsMainRegex: '(Test)?$'
+IncludeIsMainSourceRegex: ''
+IndentAccessModifiers: false
+IndentCaseLabels: false
+IndentCaseBlocks: false
+IndentGotoLabels: true
+IndentPPDirectives: None
+IndentExternBlock: AfterExternBlock
+IndentRequires:  false
+IndentWidth:     4
+IndentWrappedFunctionNames: true
+InsertTrailingCommas: None
+JavaScriptQuotes: Leave
+JavaScriptWrapImports: true
+KeepEmptyLinesAtTheStartOfBlocks: false
+LambdaBodyIndentation: Signature
+MacroBlockBegin: ''
+MacroBlockEnd:   ''
+MaxEmptyLinesToKeep: 1
+NamespaceIndentation: None
+ObjCBinPackProtocolList: Auto
+ObjCBlockIndentWidth: 4
+ObjCBreakBeforeNestedBlockParam: true
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakAssignment: 10
+PenaltyBreakBeforeFirstCallParameter: 30
+PenaltyBreakComment: 10
+PenaltyBreakFirstLessLess: 0
+PenaltyBreakOpenParenthesis: 0
+PenaltyBreakString: 10
+PenaltyBreakTemplateDeclaration: 10
+PenaltyExcessCharacter: 100
+PenaltyReturnTypeOnItsOwnLine: 60
+PenaltyIndentedWhitespace: 0
+PointerAlignment: Left
+PPIndentWidth:   -1
+ReferenceAlignment: Pointer
+ReflowComments:  false
+RemoveBracesLLVM: false
+SeparateDefinitionBlocks: Leave
+ShortNamespaceLines: 1
+SortIncludes:    Never
+SortJavaStaticImport: Before
+SortUsingDeclarations: false
+SpaceAfterCStyleCast: false
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCaseColon: false
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: Never
+SpaceBeforeParensOptions:
+  AfterControlStatements: false
+  AfterForeachMacros: false
+  AfterFunctionDefinitionName: false
+  AfterFunctionDeclarationName: false
+  AfterIfMacros:   false
+  AfterOverloadedOperator: false
+  BeforeNonEmptyParentheses: false
+SpaceAroundPointerQualifiers: Default
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyBlock: false
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles:  Never
+SpacesInConditionalStatement: false
+SpacesInContainerLiterals: false
+SpacesInCStyleCastParentheses: false
+SpacesInLineCommentPrefix:
+  Minimum:         1
+  Maximum:         -1
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+SpaceBeforeSquareBrackets: false
+BitFieldColonSpacing: Both
+Standard:        c++03
+StatementAttributeLikeMacros:
+  - Q_EMIT
+StatementMacros:
+  - Q_UNUSED
+  - QT_REQUIRE_VERSION
+TabWidth:        4
+UseCRLF:         false
+UseTab:          Never
+WhitespaceSensitiveMacros:
+  - STRINGIZE
+  - PP_STRINGIZE
+  - BOOST_PP_STRINGIZE
+  - NS_SWIFT_NAME
+  - CF_SWIFT_NAME
+...
+

+ 1 - 0
meal_pager/.gitsubtree

@@ -0,0 +1 @@
+https://github.com/leedave/flipper-zero-meal-pager main /

+ 66 - 0
meal_pager/README.md

@@ -0,0 +1,66 @@
+# Flipper Zero Meal Pager Tool
+
+## What this is?
+This app triggers restaurant pagers in a brute force manner, useful to test if devices are still functional. 
+<br><br>
+
+## Supported Pagers
+- Retekess T119
+- Retekess TD157
+- Retekess TD165
+- Retekess TD174
+
+### Features
+- Send a Range of Signals
+- Select range of stations
+- Select range of pagers
+
+## How to install on Flipper Zero
+- If you do not have one, download a firmware onto your PC via git<br>
+- Plug your Flipper Zero in via USB. <br>
+- Copy the contents of this folder into the applications_user folder of your firmware. <br> 
+
+## What does the blinking mean
+
+### Yellow Blinking LED
+Means that the SubGhz Code is being generated for the configured range
+
+### Purple Blinking LED
+Means that SubGhz Signals are being sent
+
+## Settings Explanation
+
+### Pager Type
+Sets the current Pager type your targeting. Each model uses a different encoding, you cannot trigger multiple models at once
+
+### First Station / Last Station
+Each Station has a unique id. This is important so many stations can run in one location without conflicting each other. Use these settings to define a range of stations to trigger
+
+I do not recommend a lager range than 10
+
+### Fist Pager / Last Pager
+The range of numbers on the pagers to be triggered. Most stations don't have many pagers, so a range of 0 - 31 probably is enough for all.
+
+### Signal Repeat
+How many times a single pager trigger is sent. Usually a signal is sent multiple times to combat radio noise that can cause a signal not to be recognised. 
+This is the total number of signals, so a setting of 0 will not send anything. More signals take longer, less have a higher risk of no effect. Set a number between 1 and 10. 
+
+
+## Can this Brute-Force Attacks
+This is a Proof-of-Concept. In Theory it could, but I wouldn't recommend trying it. Its annoying for people targeted and it could get you into trouble. Seriously, don't be that person, nobody will like your for it. 
+Appart from that, most pagers support 8191 Stations. Triggering ~30 Pagers per station would easily take a long time. That when only sending each trigger once. It is recommended is to repeat the signal approx 10x, so that would already take all day. 
+Also your Flipper Zero will crash in that time, as the generated signals use RAM which is limited in the device. There may be ways to work around this, but I think its better to have such a limitation.
+
+## Does this even work
+I don't know. It's based on intel collected from other people. The Flipper sends data, I checked that with a second flipper. But if the data actually triggers something is not sure. 
+
+Then run the command: 
+ ```
+.\fbt launch APPSRC=applications_user/meal_pager
+ ```
+The application will be compiled and copied onto your device. 
+
+## Thank you notes
+- [Moeker](https://github.com/moeker) and his awesome [pagger tool](https://github.com/meoker/pagger), couldn't have done this without
+- [xb8](https://github.com/xb8/t119bruteforcer) for the useful data collected
+- [Xenobyte, ShotokanZH](https://twitter.com/xenobyte_/status/1558123251276070912) for their super interesting research

+ 14 - 0
meal_pager/application.fam

@@ -0,0 +1,14 @@
+App(
+    appid="meal_pager",
+    name="Restaurant Pager Trigger",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="meal_pager_app",
+    stack_size=2 * 1024,
+    fap_icon="icons/meal_pager_10px.png",
+    fap_icon_assets="icons",
+    fap_category="Sub-Ghz",
+    fap_version="1.6",
+    fap_author="leedave",
+    fap_weburl="https://github.com/leedave/flipper-zero-meal-pager",
+    fap_description="This app triggers restaurant pagers in a brute force manner, useful to test if devices are still functional.",
+)

+ 9 - 0
meal_pager/docs/README.md

@@ -0,0 +1,9 @@
+## Meal Pager Trigger App
+
+Can make a range of Restaurant Pagers ring. 
+
+## Features
+- Support for Retekess T119, TD157, TD165, TD174
+- Select Range of Station Ids
+- Select Range of Pager Ids
+

+ 67 - 0
meal_pager/docs/changelog.md

@@ -0,0 +1,67 @@
+## v1.6
+- Fix crashes on exit on fw 0.100.3
+
+## v1.5
+- Support for ufbt compilation (assets fixed)
+
+## v1.4
+- Fixed some restrictions that prevented build under 0.99.1
+
+## v1.3
+- Fixed some memory leaks
+- Changed Subghz Protocol Registry to Fipper API conform variant
+- Version number in Start Screen
+- Code formatting
+
+## v1.2
+- Fixed Memory bug in Last Station UI
+- Added auto-correction when entries in First/Last station/pager are out of range
+
+## v1.1
+- Created a new UI Input View as FW does not supply one for numbers
+- New UI to Set First Station
+- New UI to Set Last Station
+- New UI to Set First Page
+- New UI to Set Last Pager
+- Removed Vibro/Sound settings as not used
+
+Known issues
+- After setting last station, the settings view is weird. Some kindo of memory bug. But data remains correct. 
+- Extensive use can cause crashes, must be some memory leak left
+
+
+## v1.0
+- Added support for TD174
+
+## v0.9
+- Added support for TD165
+
+## v0.8
+- Added Repeats feature 
+- Repeats configuration in settings
+- Usage of repeats in T119
+- Usage of repeats in TD157
+
+## v0.7
+
+- Added support for TD157
+- Some log & comment cleaning
+- Moved common code to new location
+- Removed testing assets
+- Fixed bad menu label (from "scene1" to "send data")
+
+## v0.6
+
+- Display when data is being generated and when it is being sent
+- Fixed issue where callbacks were sent infitiv when leaving the transmission page
+- Fixed blinking remaining when leaving transmission page prematurely
+- Set default last station Number lower to prevent crashes on first try
+
+
+## v0.5
+
+Compiled .sub data is read out and sent via SubGhz. Currently only support for T119. 
+
+## v0.1
+
+Can now generate a temporary .sub file for Retekess T119 Pager triggers. Must still be run via SubGhz App

+ 390 - 0
meal_pager/helpers/gui/int_input.c

@@ -0,0 +1,390 @@
+#include "int_input.h"
+
+#include <gui/elements.h>
+#include <furi.h>
+#include "meal_pager_icons.h"
+
+/** IntInput type */
+struct IntInput {
+    View* view;
+};
+
+typedef struct {
+    const char text;
+    const uint8_t x;
+    const uint8_t y;
+} IntInputKey;
+
+typedef struct {
+    const char* header;
+    char* text_buffer;
+    size_t text_buffer_size;
+    bool clear_default_text;
+
+    IntInputCallback callback;
+    void* callback_context;
+
+    int8_t selected_row;
+    uint8_t selected_column;
+} IntInputModel;
+
+static const uint8_t keyboard_origin_x = 7;
+static const uint8_t keyboard_origin_y = 31;
+static const uint8_t keyboard_row_count = 2;
+static const uint8_t enter_symbol = '\r';
+static const uint8_t backspace_symbol = '\b';
+
+static const IntInputKey keyboard_keys_row_1[] = {
+    {'0', 0, 12},
+    {'1', 11, 12},
+    {'2', 22, 12},
+    {'3', 33, 12},
+    {'4', 44, 12},
+    {backspace_symbol, 103, 4},
+};
+
+static const IntInputKey keyboard_keys_row_2[] = {
+    {'5', 0, 26},
+    {'6', 11, 26},
+    {'7', 22, 26},
+    {'8', 33, 26},
+    {'9', 44, 26},
+    {enter_symbol, 95, 17},
+};
+
+/** Get row size
+ *
+ * @param      row_index  Index of row
+ *
+ * @return     uint8_t Row size
+ */
+static uint8_t int_input_get_row_size(uint8_t row_index) {
+    uint8_t row_size = 0;
+
+    switch(row_index + 1) {
+    case 1:
+        row_size = COUNT_OF(keyboard_keys_row_1);
+        break;
+    case 2:
+        row_size = COUNT_OF(keyboard_keys_row_2);
+        break;
+    default:
+        furi_crash();
+    }
+
+    return row_size;
+}
+
+/** Get row pointer
+ *
+ * @param      row_index  Index of row
+ *
+ * @return     const IntInputKey* Row pointer
+ */
+static const IntInputKey* int_input_get_row(uint8_t row_index) {
+    const IntInputKey* row = NULL;
+
+    switch(row_index + 1) {
+    case 1:
+        row = keyboard_keys_row_1;
+        break;
+    case 2:
+        row = keyboard_keys_row_2;
+        break;
+    default:
+        furi_crash();
+    }
+
+    return row;
+}
+
+/** Draw input box (common view)
+ *
+ * @param      canvas  The canvas
+ * @param      model   The model
+ */
+static void int_input_draw_input(Canvas* canvas, IntInputModel* model) {
+    const uint8_t text_x = 8;
+    const uint8_t text_y = 25;
+
+    elements_slightly_rounded_frame(canvas, 6, 14, 116, 15);
+
+    const char* text = model->text_buffer;
+    canvas_draw_str(canvas, text_x, text_y, text);
+}
+
+static void int_input_backspace_cb(IntInputModel* model) {
+    uint8_t text_length = model->clear_default_text ? 1 : strlen(model->text_buffer);
+    if(text_length > 0) {
+        model->text_buffer[text_length - 1] = 0;
+    }
+}
+
+/** Handle up button
+ *
+ * @param      model  The model
+ */
+static void int_input_handle_up(IntInputModel* model) {
+    if(model->selected_row > 0) {
+        model->selected_row--;
+    }
+}
+
+/** Handle down button
+ *
+ * @param      model  The model
+ */
+static void int_input_handle_down(IntInputModel* model) {
+    if(model->selected_row < keyboard_row_count - 1) {
+        model->selected_row += 1;
+    }
+}
+
+/** Handle left button
+ *
+ * @param      model  The model
+ */
+static void int_input_handle_left(IntInputModel* model) {
+    if(model->selected_column > 0) {
+        model->selected_column--;
+    } else {
+        model->selected_column = int_input_get_row_size(model->selected_row) - 1;
+    }
+}
+
+/** Handle right button
+ *
+ * @param      model  The model
+ */
+static void int_input_handle_right(IntInputModel* model) {
+    if(model->selected_column < int_input_get_row_size(model->selected_row) - 1) {
+        model->selected_column++;
+    } else {
+        model->selected_column = 0;
+    }
+}
+
+/** Handle OK button
+ *
+ * @param      model  The model
+ */
+static void int_input_handle_ok(IntInputModel* model) {
+    char selected = int_input_get_row(model->selected_row)[model->selected_column].text;
+    size_t text_length = strlen(model->text_buffer);
+    if(selected == enter_symbol) {
+        model->callback(model->callback_context);
+    } else if(selected == backspace_symbol) {
+        int_input_backspace_cb(model);
+    } else {
+        if(model->clear_default_text) {
+            text_length = 0;
+        }
+        if(text_length < (model->text_buffer_size - 1)) {
+            model->text_buffer[text_length] = selected;
+            model->text_buffer[text_length + 1] = 0;
+        }
+    }
+    model->clear_default_text = false;
+}
+
+/** Draw callback
+ *
+ * @param      canvas  The canvas
+ * @param      _model  The model
+ */
+static void int_input_view_draw_callback(Canvas* canvas, void* _model) {
+    IntInputModel* model = _model;
+    uint8_t text_length = model->text_buffer ? strlen(model->text_buffer) : 0;
+    UNUSED(text_length);
+
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+
+    int_input_draw_input(canvas, model);
+
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str(canvas, 2, 9, model->header);
+    canvas_set_font(canvas, FontKeyboard);
+    // Draw keyboard
+    for(uint8_t row = 0; row < keyboard_row_count; row++) {
+        const uint8_t column_count = int_input_get_row_size(row);
+        const IntInputKey* keys = int_input_get_row(row);
+
+        for(size_t column = 0; column < column_count; column++) {
+            if(keys[column].text == enter_symbol) {
+                canvas_set_color(canvas, ColorBlack);
+                if(model->selected_row == row && model->selected_column == column) {
+                    canvas_draw_icon(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        &I_KeySaveSelected_24x11);
+                } else {
+                    canvas_draw_icon(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        &I_KeySave_24x11);
+                }
+            } else if(keys[column].text == backspace_symbol) {
+                canvas_set_color(canvas, ColorBlack);
+                if(model->selected_row == row && model->selected_column == column) {
+                    canvas_draw_icon(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        &I_KeyBackspaceSelected_16x9);
+                } else {
+                    canvas_draw_icon(
+                        canvas,
+                        keyboard_origin_x + keys[column].x,
+                        keyboard_origin_y + keys[column].y,
+                        &I_KeyBackspace_16x9);
+                }
+            } else {
+                if(model->selected_row == row && model->selected_column == column) {
+                    canvas_set_color(canvas, ColorBlack);
+                    canvas_draw_box(
+                        canvas,
+                        keyboard_origin_x + keys[column].x - 3,
+                        keyboard_origin_y + keys[column].y - 10,
+                        11,
+                        13);
+                    canvas_set_color(canvas, ColorWhite);
+                } else if(model->selected_row == -1 && row == 0 && model->selected_column == column) {
+                    canvas_set_color(canvas, ColorBlack);
+                    canvas_draw_frame(
+                        canvas,
+                        keyboard_origin_x + keys[column].x - 3,
+                        keyboard_origin_y + keys[column].y - 10,
+                        11,
+                        13);
+                } else {
+                    canvas_set_color(canvas, ColorBlack);
+                }
+
+                canvas_draw_glyph(
+                    canvas,
+                    keyboard_origin_x + keys[column].x,
+                    keyboard_origin_y + keys[column].y,
+                    keys[column].text);
+            }
+        }
+    }
+}
+
+/** Input callback
+ *
+ * @param      event    The event
+ * @param      context  The context
+ *
+ * @return     true
+ * @return     false
+ */
+static bool int_input_view_input_callback(InputEvent* event, void* context) {
+    IntInput* int_input = context;
+    furi_assert(int_input);
+
+    bool consumed = false;
+
+    // Fetch the model
+    IntInputModel* model = view_get_model(int_input->view);
+
+    if(event->type == InputTypeShort || event->type == InputTypeLong ||
+       event->type == InputTypeRepeat) {
+        consumed = true;
+        switch(event->key) {
+        case InputKeyLeft:
+            int_input_handle_left(model);
+            break;
+        case InputKeyRight:
+            int_input_handle_right(model);
+            break;
+        case InputKeyUp:
+            int_input_handle_up(model);
+            break;
+        case InputKeyDown:
+            int_input_handle_down(model);
+            break;
+        case InputKeyOk:
+            int_input_handle_ok(model);
+            break;
+        default:
+            consumed = false;
+            break;
+        }
+    }
+
+    // commit view
+    view_commit_model(int_input->view, consumed);
+
+    return consumed;
+}
+
+void int_input_reset(IntInput* int_input) {
+    FURI_LOG_D("INT_INPUT", "Resetting Model");
+    furi_assert(int_input);
+    with_view_model(
+        int_input->view,
+        IntInputModel * model,
+        {
+            model->header = "";
+            model->selected_row = 0;
+            model->selected_column = 0;
+            model->clear_default_text = false;
+            model->text_buffer = "";
+            model->text_buffer_size = 0;
+            model->callback = NULL;
+            model->callback_context = NULL;
+        },
+        true);
+}
+
+IntInput* int_input_alloc() {
+    IntInput* int_input = malloc(sizeof(IntInput));
+    int_input->view = view_alloc();
+    view_set_context(int_input->view, int_input);
+    view_allocate_model(int_input->view, ViewModelTypeLocking, sizeof(IntInputModel));
+    view_set_draw_callback(int_input->view, int_input_view_draw_callback);
+    view_set_input_callback(int_input->view, int_input_view_input_callback);
+
+    int_input_reset(int_input);
+
+    return int_input;
+}
+
+void int_input_free(IntInput* int_input) {
+    furi_assert(int_input);
+    view_free(int_input->view);
+    free(int_input);
+}
+
+View* int_input_get_view(IntInput* int_input) {
+    furi_assert(int_input);
+    return int_input->view;
+}
+
+void int_input_set_result_callback(
+    IntInput* int_input,
+    IntInputCallback callback,
+    void* callback_context,
+    char* text_buffer,
+    size_t text_buffer_size,
+    bool clear_default_text) {
+    with_view_model(
+        int_input->view,
+        IntInputModel * model,
+        {
+            model->callback = callback;
+            model->callback_context = callback_context;
+            model->text_buffer = text_buffer;
+            model->text_buffer_size = text_buffer_size;
+            model->clear_default_text = clear_default_text;
+        },
+        true);
+}
+
+void int_input_set_header_text(IntInput* int_input, const char* text) {
+    with_view_model(
+        int_input->view, IntInputModel * model, { model->header = text; }, true);
+}

+ 71 - 0
meal_pager/helpers/gui/int_input.h

@@ -0,0 +1,71 @@
+/**
+ * @file int_input.h
+ * GUI: Integer string keyboard view module API
+ */
+
+#pragma once
+
+#include <gui/view.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** Int input anonymous structure  */
+typedef struct IntInput IntInput;
+
+/** callback that is executed on save button press */
+typedef void (*IntInputCallback)(void* context);
+
+/** callback that is executed when byte buffer is changed */
+typedef void (*IntChangedCallback)(void* context);
+
+/** Allocate and initialize Int input. This Int input is used to enter Ints.
+ *
+ * @return     IntInput instance pointer
+ */
+IntInput* int_input_alloc();
+
+/** Deinitialize and free byte input
+ *
+ * @param      int_input  Int input instance
+ */
+void int_input_free(IntInput* int_input);
+
+/** Get byte input view
+ *
+ * @param      int_input  byte input instance
+ *
+ * @return     View instance that can be used for embedding
+ */
+View* int_input_get_view(IntInput* int_input);
+
+/** Set byte input result callback
+ *
+ * @param      int_input          byte input instance
+ * @param      input_callback     input callback fn
+ * @param      changed_callback   changed callback fn
+ * @param      callback_context   callback context
+ * @param      text_buffer        buffer to use
+ * @param      text_buffer_size   buffer length
+ * @param      clear_default_text clear previous entry
+ */
+
+void int_input_set_result_callback(
+    IntInput* int_input,
+    IntInputCallback input_callback,
+    void* callback_context,
+    char* text_buffer,
+    size_t text_buffer_size,
+    bool clear_default_text);
+
+/** Set byte input header text
+ *
+ * @param      int_input  byte input instance
+ * @param      text        text to be shown
+ */
+void int_input_set_header_text(IntInput* int_input, const char* text);
+
+#ifdef __cplusplus
+}
+#endif

+ 80 - 0
meal_pager/helpers/meal_pager_calc.c

@@ -0,0 +1,80 @@
+#include "meal_pager_calc.h"
+
+void customConcat(char* dest, const char* src) {
+    // Find the end of the destination string
+    while(*dest != '\0') {
+        dest++;
+    }
+
+    // Copy characters from src to dest
+    while(*src != '\0') {
+        *dest = *src;
+        dest++;
+        src++;
+    }
+
+    // Null-terminate the concatenated string
+    *dest = '\0';
+}
+
+char* encManchester(const char* bits, int mode) {
+    // Allocate memory for the result string
+    char* res = (char*)malloc((strlen(bits) * 2 + 1) * sizeof(char));
+
+    int index = 0;
+    for(int i = 0; bits[i] != '\0'; i++) {
+        char c = bits[i];
+        if(c == '0') {
+            if(mode) {
+                res[index++] = '1';
+                res[index++] = '0';
+            } else {
+                res[index++] = '0';
+                res[index++] = '1';
+            }
+        } else if(c == '1') {
+            if(mode) {
+                res[index++] = '0';
+                res[index++] = '1';
+            } else {
+                res[index++] = '1';
+                res[index++] = '0';
+            }
+        } else {
+            // Handle 'EE' case (error)
+            res[index++] = 'E';
+            res[index++] = 'E';
+        }
+    }
+
+    // Null-terminate the result string
+    res[index] = '\0';
+
+    return res;
+}
+
+void uint32ToBinaray(uint32_t number, char* str, int8_t length) {
+    int i = 0;
+    length--; // count length without 0
+    for(i = length; i >= 0; i--) {
+        // Bitwise AND extration of the i-th bit
+        int bit = (number >> i) & 1;
+        // convert the bit to a character of 1 or 0
+        str[length - i] = bit + '0';
+    }
+    // Terminate the string
+    str[length + 1] = '\0';
+}
+
+void reverse(char* str) {
+    int length = strlen(str);
+    int start = 0;
+    int end = length - 1;
+    while(start < end) {
+        char temp = str[start];
+        str[start] = str[end];
+        str[end] = temp;
+        start++;
+        end--;
+    }
+}

+ 12 - 0
meal_pager/helpers/meal_pager_calc.h

@@ -0,0 +1,12 @@
+
+#pragma once
+
+#include "../meal_pager_i.h"
+
+char* encManchester(const char* bits, int mode);
+
+void uint32ToBinaray(uint32_t number, char* str, int8_t length);
+
+void reverse(char* str);
+
+void customConcat(char* dest, const char* src);

+ 67 - 0
meal_pager/helpers/meal_pager_custom_event.h

@@ -0,0 +1,67 @@
+#pragma once
+
+typedef enum {
+    Meal_PagerCustomEventStartscreenUp,
+    Meal_PagerCustomEventStartscreenDown,
+    Meal_PagerCustomEventStartscreenLeft,
+    Meal_PagerCustomEventStartscreenRight,
+    Meal_PagerCustomEventStartscreenOk,
+    Meal_PagerCustomEventStartscreenBack,
+    Meal_PagerCustomEventTransmitUp,
+    Meal_PagerCustomEventTransmitDown,
+    Meal_PagerCustomEventTransmitLeft,
+    Meal_PagerCustomEventTransmitRight,
+    Meal_PagerCustomEventTransmitOk,
+    Meal_PagerCustomEventTransmitBack,
+    Meal_PagerCustomEventScene2Up,
+    Meal_PagerCustomEventScene2Down,
+    Meal_PagerCustomEventScene2Left,
+    Meal_PagerCustomEventScene2Right,
+    Meal_PagerCustomEventScene2Ok,
+    Meal_PagerCustomEventScene2Back,
+    Meal_PagerCustomEventViewTransmitterBack,
+    Meal_PagerCustomEventViewTransmitterSendStart,
+    Meal_PagerCustomEventViewTransmitterSendStop,
+    Meal_PagerCustomEventViewTransmitterError,
+    Meal_PagerCustomerEventIntInput,
+    Meal_PagerCustomEventViewIntInputOk,
+} Meal_PagerCustomEvent;
+
+enum Meal_PagerCustomEventType {
+    // Reserve first 100 events for button types and indexes, starting from 0
+    Meal_PagerCustomEventMenuVoid,
+    Meal_PagerCustomEventMenuSelected,
+};
+
+#pragma pack(push, 1)
+typedef union {
+    uint32_t packed_value;
+    struct {
+        uint16_t type;
+        int16_t value;
+    } content;
+} Meal_PagerCustomEventMenu;
+#pragma pack(pop)
+
+static inline uint32_t meal_pager_custom_menu_event_pack(uint16_t type, int16_t value) {
+    Meal_PagerCustomEventMenu event = {.content = {.type = type, .value = value}};
+    return event.packed_value;
+}
+static inline void
+    meal_pager_custom_menu_event_unpack(uint32_t packed_value, uint16_t* type, int16_t* value) {
+    Meal_PagerCustomEventMenu event = {.packed_value = packed_value};
+    if(type) *type = event.content.type;
+    if(value) *value = event.content.value;
+}
+
+static inline uint16_t meal_pager_custom_menu_event_get_type(uint32_t packed_value) {
+    uint16_t type;
+    meal_pager_custom_menu_event_unpack(packed_value, &type, NULL);
+    return type;
+}
+
+static inline int16_t meal_pager_custom_menu_event_get_value(uint32_t packed_value) {
+    int16_t value;
+    meal_pager_custom_menu_event_unpack(packed_value, NULL, &value);
+    return value;
+}

+ 34 - 0
meal_pager/helpers/meal_pager_haptic.c

@@ -0,0 +1,34 @@
+#include "meal_pager_haptic.h"
+
+void meal_pager_play_happy_bump(void* context) {
+    Meal_Pager* app = context;
+    if(app->haptic != 1) {
+        return;
+    }
+    notification_message(app->notification, &sequence_set_vibro_on);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 20);
+    notification_message(app->notification, &sequence_reset_vibro);
+}
+
+void meal_pager_play_bad_bump(void* context) {
+    Meal_Pager* app = context;
+    if(app->haptic != 1) {
+        return;
+    }
+    notification_message(app->notification, &sequence_set_vibro_on);
+    furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+    notification_message(app->notification, &sequence_reset_vibro);
+}
+
+void meal_pager_play_long_bump(void* context) {
+    Meal_Pager* app = context;
+    if(app->haptic != 1) {
+        return;
+    }
+    for(int i = 0; i < 4; i++) {
+        notification_message(app->notification, &sequence_set_vibro_on);
+        furi_thread_flags_wait(0, FuriFlagWaitAny, 50);
+        notification_message(app->notification, &sequence_reset_vibro);
+        furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+    }
+}

+ 10 - 0
meal_pager/helpers/meal_pager_haptic.h

@@ -0,0 +1,10 @@
+#pragma once
+
+#include <notification/notification_messages.h>
+#include "../meal_pager_i.h"
+
+void meal_pager_play_happy_bump(void* context);
+
+void meal_pager_play_bad_bump(void* context);
+
+void meal_pager_play_long_bump(void* context);

+ 59 - 0
meal_pager/helpers/meal_pager_led.c

@@ -0,0 +1,59 @@
+#include "meal_pager_led.h"
+
+void meal_pager_blink_start_subghz(Meal_Pager* app) {
+    furi_assert(app);
+    if(app->led == 1) {
+        notification_message(app->notification, &sequence_blink_stop);
+        notification_message(app->notification, &sequence_blink_start_magenta);
+    }
+}
+
+void meal_pager_blink_start_compile(Meal_Pager* app) {
+    furi_assert(app);
+    if(app->led == 1) {
+        notification_message(app->notification, &sequence_blink_stop);
+        notification_message(app->notification, &sequence_blink_start_yellow);
+    }
+}
+
+void meal_pager_blink_stop(Meal_Pager* app) {
+    furi_assert(app);
+    notification_message(app->notification, &sequence_blink_stop);
+}
+
+void meal_pager_led_set_rgb(void* context, int red, int green, int blue) {
+    Meal_Pager* app = context;
+    if(app->led != 1) {
+        return;
+    }
+    NotificationMessage notification_led_message_1;
+    notification_led_message_1.type = NotificationMessageTypeLedRed;
+    NotificationMessage notification_led_message_2;
+    notification_led_message_2.type = NotificationMessageTypeLedGreen;
+    NotificationMessage notification_led_message_3;
+    notification_led_message_3.type = NotificationMessageTypeLedBlue;
+
+    notification_led_message_1.data.led.value = red;
+    notification_led_message_2.data.led.value = green;
+    notification_led_message_3.data.led.value = blue;
+    const NotificationSequence notification_sequence = {
+        &notification_led_message_1,
+        &notification_led_message_2,
+        &notification_led_message_3,
+        &message_do_not_reset,
+        NULL,
+    };
+    notification_message(app->notification, &notification_sequence);
+    furi_thread_flags_wait(
+        0, FuriFlagWaitAny, 10); //Delay, prevent removal from RAM before LED value set
+}
+
+void meal_pager_led_reset(void* context) {
+    Meal_Pager* app = context;
+    notification_message(app->notification, &sequence_reset_red);
+    notification_message(app->notification, &sequence_reset_green);
+    notification_message(app->notification, &sequence_reset_blue);
+
+    furi_thread_flags_wait(
+        0, FuriFlagWaitAny, 300); //Delay, prevent removal from RAM before LED value set
+}

+ 13 - 0
meal_pager/helpers/meal_pager_led.h

@@ -0,0 +1,13 @@
+#pragma once
+
+#include "../meal_pager_i.h"
+
+void meal_pager_blink_start_subghz(Meal_Pager* app);
+
+void meal_pager_blink_start_compile(Meal_Pager* app);
+
+void meal_pager_blink_stop(Meal_Pager* app);
+
+void meal_pager_led_set_rgb(void* context, int red, int green, int blue);
+
+void meal_pager_led_reset(void* context);

+ 25 - 0
meal_pager/helpers/meal_pager_speaker.c

@@ -0,0 +1,25 @@
+#include "meal_pager_speaker.h"
+
+#define NOTE_INPUT 587.33f
+
+void meal_pager_play_input_sound(void* context) {
+    Meal_Pager* app = context;
+    if(app->speaker != 1) {
+        return;
+    }
+    float volume = 1.0f;
+    if(furi_hal_speaker_is_mine() || furi_hal_speaker_acquire(30)) {
+        furi_hal_speaker_start(NOTE_INPUT, volume);
+    }
+}
+
+void meal_pager_stop_all_sound(void* context) {
+    Meal_Pager* app = context;
+    if(app->speaker != 1) {
+        return;
+    }
+    if(furi_hal_speaker_is_mine()) {
+        furi_hal_speaker_stop();
+        furi_hal_speaker_release();
+    }
+}

+ 8 - 0
meal_pager/helpers/meal_pager_speaker.h

@@ -0,0 +1,8 @@
+#pragma once
+
+#include "../meal_pager_i.h"
+
+#define NOTE_INPUT 587.33f
+
+void meal_pager_play_input_sound(void* context);
+void meal_pager_stop_all_sound(void* context);

+ 248 - 0
meal_pager/helpers/meal_pager_storage.c

@@ -0,0 +1,248 @@
+#include "meal_pager_storage.h"
+
+static Storage* meal_pager_open_storage() {
+    return furi_record_open(RECORD_STORAGE);
+}
+
+static void meal_pager_close_storage() {
+    furi_record_close(RECORD_STORAGE);
+}
+
+static void meal_pager_close_config_file(FlipperFormat* file) {
+    if(file == NULL) return;
+    flipper_format_rewind(file);
+    flipper_format_file_close(file);
+    flipper_format_free(file);
+}
+
+bool meal_pager_save_subghz_buffer_file_start(
+    void* context,
+    FlipperFormat* ff,
+    Storage* storage,
+    char* frequency) {
+    // SubGhz TXRX can only be loaded with files, makes sense as to save RAM
+    Meal_Pager* app = context;
+    UNUSED(app);
+    bool success = false;
+    FURI_LOG_D(TAG, "Creating Temp File");
+    //Storage* storage = furi_record_open(RECORD_STORAGE);
+    //FlipperFormat* ff = flipper_format_file_alloc(storage);
+
+    // Overwrite wont work, so delete first
+    if(storage_file_exists(storage, MEAL_PAGER_TMP_FILE)) {
+        bool stored = storage_simply_remove(storage, MEAL_PAGER_TMP_FILE);
+        if(!stored) {
+            FURI_LOG_D(TAG, "Cannot remove file, seems to be open");
+            return success;
+        }
+    }
+
+    // Open File, create if not exists
+    if(!storage_common_stat(storage, MEAL_PAGER_TMP_FILE, NULL) == FSE_OK) {
+        FURI_LOG_D(
+            TAG, "Config file %s is not found. Will create new.", MEAL_PAGER_SETTINGS_SAVE_PATH);
+        if(storage_common_stat(storage, CONFIG_FILE_DIRECTORY_PATH, NULL) == FSE_NOT_EXIST) {
+            FURI_LOG_D(
+                TAG, "Directory %s doesn't exist. Will create new.", CONFIG_FILE_DIRECTORY_PATH);
+            if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) {
+                FURI_LOG_D(TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH);
+            }
+        }
+    }
+
+    if(!flipper_format_file_open_new(ff, MEAL_PAGER_TMP_FILE)) {
+        //totp_close_config_file(fff_file);
+        FURI_LOG_D(TAG, "Error creating new file %s", MEAL_PAGER_TMP_FILE);
+        meal_pager_close_storage();
+        return success;
+    }
+
+    success = flipper_format_write_header_cstr(
+                  ff, MEAL_PAGER_SUBGHZ_FILE_TYPE, MEAL_PAGER_SUBGHZ_FILE_VERSION) &&
+              flipper_format_write_string_cstr(ff, "Frequency", frequency) &&
+              flipper_format_write_string_cstr(ff, "Preset", MEAL_PAGER_SUBGHZ_FILE_PRESET) &&
+              flipper_format_write_string_cstr(ff, "Protocol", MEAL_PAGER_SUBGHZ_FILE_Protocol);
+    return success;
+}
+
+void meal_pager_save_subghz_buffer_stop(void* context, FlipperFormat* ff) {
+    Meal_Pager* app = context;
+    UNUSED(app);
+    FURI_LOG_D(TAG, "Closing Temp File");
+    if(!flipper_format_rewind(ff)) {
+        meal_pager_close_config_file(ff);
+        FURI_LOG_E(TAG, "Rewind error");
+        meal_pager_close_storage();
+        return;
+    }
+
+    meal_pager_close_config_file(ff);
+    meal_pager_close_storage();
+}
+
+void meal_pager_save_settings(void* context) {
+    Meal_Pager* app = context;
+    if(app->save_settings == 0) {
+        FURI_LOG_D(TAG, "Skipping Save because Disabled");
+        return;
+    }
+
+    FURI_LOG_D(TAG, "Saving Settings to File");
+    Storage* storage = meal_pager_open_storage();
+    FlipperFormat* fff_file = flipper_format_file_alloc(storage);
+
+    // Overwrite wont work, so delete first
+    if(storage_file_exists(storage, MEAL_PAGER_SETTINGS_SAVE_PATH)) {
+        storage_simply_remove(storage, MEAL_PAGER_SETTINGS_SAVE_PATH);
+    }
+
+    // Open File, create if not exists
+    if(!storage_common_stat(storage, MEAL_PAGER_SETTINGS_SAVE_PATH, NULL) == FSE_OK) {
+        FURI_LOG_D(
+            TAG, "Config file %s is not found. Will create new.", MEAL_PAGER_SETTINGS_SAVE_PATH);
+        if(storage_common_stat(storage, CONFIG_FILE_DIRECTORY_PATH, NULL) == FSE_NOT_EXIST) {
+            FURI_LOG_D(
+                TAG, "Directory %s doesn't exist. Will create new.", CONFIG_FILE_DIRECTORY_PATH);
+            if(!storage_simply_mkdir(storage, CONFIG_FILE_DIRECTORY_PATH)) {
+                FURI_LOG_E(TAG, "Error creating directory %s", CONFIG_FILE_DIRECTORY_PATH);
+            }
+        }
+    }
+
+    if(!flipper_format_file_open_new(fff_file, MEAL_PAGER_SETTINGS_SAVE_PATH)) {
+        //totp_close_config_file(fff_file);
+        FURI_LOG_E(TAG, "Error creating new file %s", MEAL_PAGER_SETTINGS_SAVE_PATH);
+        meal_pager_close_storage();
+        return;
+    }
+
+    // Store Settings
+    flipper_format_write_header_cstr(
+        fff_file, MEAL_PAGER_SETTINGS_HEADER, MEAL_PAGER_SETTINGS_FILE_VERSION);
+    flipper_format_write_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_PAGER_TYPE, &app->pager_type, 1);
+    flipper_format_write_uint32(
+        fff_file, MEAL_PAGER_SETTINGS_KEY_FIRST_STATION, &app->first_station, 1);
+    flipper_format_write_uint32(
+        fff_file, MEAL_PAGER_SETTINGS_KEY_LAST_STATION, &app->last_station, 1);
+    flipper_format_write_uint32(
+        fff_file, MEAL_PAGER_SETTINGS_KEY_FIRST_PAGER, &app->first_pager, 1);
+    flipper_format_write_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_LAST_PAGER, &app->last_pager, 1);
+    flipper_format_write_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_LAST_PAGER, &app->last_pager, 1);
+    flipper_format_write_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_REPEATS, &app->repeats, 1);
+    flipper_format_write_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_HAPTIC, &app->haptic, 1);
+    flipper_format_write_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_SPEAKER, &app->speaker, 1);
+    flipper_format_write_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_LED, &app->led, 1);
+    flipper_format_write_uint32(
+        fff_file, MEAL_PAGER_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1);
+
+    if(!flipper_format_rewind(fff_file)) {
+        meal_pager_close_config_file(fff_file);
+        FURI_LOG_E(TAG, "Rewind error");
+        meal_pager_close_storage();
+        return;
+    }
+
+    meal_pager_close_config_file(fff_file);
+    meal_pager_close_storage();
+}
+
+void meal_pager_read_settings(void* context) {
+    Meal_Pager* app = context;
+    Storage* storage = meal_pager_open_storage();
+    FlipperFormat* fff_file = flipper_format_file_alloc(storage);
+
+    if(storage_common_stat(storage, MEAL_PAGER_SETTINGS_SAVE_PATH, NULL) != FSE_OK) {
+        meal_pager_close_config_file(fff_file);
+        meal_pager_close_storage();
+        return;
+    }
+    uint32_t file_version;
+    FuriString* temp_str = furi_string_alloc();
+
+    if(!flipper_format_file_open_existing(fff_file, MEAL_PAGER_SETTINGS_SAVE_PATH)) {
+        FURI_LOG_E(TAG, "Cannot open file %s", MEAL_PAGER_SETTINGS_SAVE_PATH);
+        meal_pager_close_config_file(fff_file);
+        meal_pager_close_storage();
+        return;
+    }
+
+    if(!flipper_format_read_header(fff_file, temp_str, &file_version)) {
+        FURI_LOG_E(TAG, "Missing Header Data");
+        meal_pager_close_config_file(fff_file);
+        meal_pager_close_storage();
+        return;
+    }
+
+    if(file_version < MEAL_PAGER_SETTINGS_FILE_VERSION) {
+        FURI_LOG_I(TAG, "old config version, will be removed.");
+        meal_pager_close_config_file(fff_file);
+        meal_pager_close_storage();
+        return;
+    }
+
+    flipper_format_read_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_PAGER_TYPE, &app->pager_type, 1);
+    meal_pager_set_max_values(app);
+    flipper_format_read_uint32(
+        fff_file, MEAL_PAGER_SETTINGS_KEY_FIRST_STATION, &app->first_station, 1);
+    flipper_format_read_uint32(
+        fff_file, MEAL_PAGER_SETTINGS_KEY_LAST_STATION, &app->last_station, 1);
+    flipper_format_read_uint32(
+        fff_file, MEAL_PAGER_SETTINGS_KEY_FIRST_PAGER, &app->first_pager, 1);
+    flipper_format_read_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_LAST_PAGER, &app->last_pager, 1);
+    flipper_format_read_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_REPEATS, &app->repeats, 1);
+    flipper_format_read_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_HAPTIC, &app->haptic, 1);
+    flipper_format_read_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_SPEAKER, &app->speaker, 1);
+    flipper_format_read_uint32(fff_file, MEAL_PAGER_SETTINGS_KEY_LED, &app->led, 1);
+    flipper_format_read_uint32(
+        fff_file, MEAL_PAGER_SETTINGS_KEY_SAVE_SETTINGS, &app->save_settings, 1);
+
+    flipper_format_rewind(fff_file);
+
+    furi_string_free(temp_str);
+
+    meal_pager_close_config_file(fff_file);
+    meal_pager_close_storage();
+}
+
+void meal_pager_set_max_values(void* context) {
+    Meal_Pager* app = context;
+    switch(app->pager_type) {
+    case Meal_PagerPagerTypeT119:
+    case Meal_PagerPagerTypeTD165:
+        app->max_station = 8191;
+        app->max_pager = 999;
+        break;
+    case Meal_PagerPagerTypeTD174:
+        app->max_station = 8191;
+        app->max_pager = 10;
+        break;
+    case Meal_PagerPagerTypeTD157:
+        app->max_station = 1023;
+        app->max_pager = 999;
+        break;
+    }
+    if(app->first_station > app->max_station) {
+        app->first_station = app->max_station;
+        snprintf(app->text_store[0], 20, "%lu", app->first_station);
+    }
+    if(app->last_station > app->max_station) {
+        app->last_station = app->max_station;
+        snprintf(app->text_store[1], 20, "%lu", app->last_station);
+    }
+    if(app->last_station < app->first_station) {
+        app->last_station = app->first_station;
+        snprintf(app->text_store[1], 20, "%lu", app->last_station);
+    }
+    if(app->first_pager > app->max_pager) {
+        app->first_pager = app->max_pager;
+        snprintf(app->text_store[2], 20, "%lu", app->first_pager);
+    }
+    if(app->last_pager > app->max_pager) {
+        app->last_pager = app->max_pager;
+        snprintf(app->text_store[3], 20, "%lu", app->last_pager);
+    }
+    if(app->last_pager < app->first_pager) {
+        app->last_pager = app->first_pager;
+        snprintf(app->text_store[3], 20, "%lu", app->last_pager);
+    }
+}

+ 42 - 0
meal_pager/helpers/meal_pager_storage.h

@@ -0,0 +1,42 @@
+#pragma once
+
+#include <stdlib.h>
+#include <string.h>
+#include <storage/storage.h>
+#include <flipper_format/flipper_format_i.h>
+#include "../meal_pager_i.h"
+
+#define MEAL_PAGER_SETTINGS_FILE_VERSION 2
+#define CONFIG_FILE_DIRECTORY_PATH EXT_PATH("apps_data/meal_pager")
+#define MEAL_PAGER_SETTINGS_SAVE_PATH CONFIG_FILE_DIRECTORY_PATH "/meal_pager.conf"
+#define MEAL_PAGER_SETTINGS_SAVE_PATH_TMP MEAL_PAGER_SETTINGS_SAVE_PATH ".tmp"
+#define MEAL_PAGER_SETTINGS_HEADER "Meal_Pager Config File"
+#define MEAL_PAGER_SETTINGS_KEY_PAGER_TYPE "Pager Type"
+#define MEAL_PAGER_SETTINGS_KEY_FIRST_STATION "First Station"
+#define MEAL_PAGER_SETTINGS_KEY_LAST_STATION "Last Station"
+#define MEAL_PAGER_SETTINGS_KEY_FIRST_PAGER "First Pager"
+#define MEAL_PAGER_SETTINGS_KEY_LAST_PAGER "Last Pager"
+#define MEAL_PAGER_SETTINGS_KEY_REPEATS "Repeats"
+#define MEAL_PAGER_SETTINGS_KEY_HAPTIC "Haptic"
+#define MEAL_PAGER_SETTINGS_KEY_LED "Led"
+#define MEAL_PAGER_SETTINGS_KEY_SPEAKER "Speaker"
+#define MEAL_PAGER_SETTINGS_KEY_SAVE_SETTINGS "SaveSettings"
+#define MEAL_PAGER_TMP_FILE CONFIG_FILE_DIRECTORY_PATH "/tmp.sub"
+#define MEAL_PAGER_SUBGHZ_FILE_TYPE "Flipper SubGhz RAW File"
+#define MEAL_PAGER_SUBGHZ_FILE_VERSION 1
+#define MEAL_PAGER_SUBGHZ_FILE_FREQUENCY "433920000"
+#define MEAL_PAGER_SUBGHZ_FILE_ALT_FREQUENCY "433889000"
+#define MEAL_PAGER_SUBGHZ_FILE_PRESET "FuriHalSubGhzPresetOok650Async"
+#define MEAL_PAGER_SUBGHZ_FILE_Protocol "RAW"
+
+bool meal_pager_save_subghz_buffer_file_start(
+    void* context,
+    FlipperFormat* ff,
+    Storage* storage,
+    char* frequency);
+
+void meal_pager_save_subghz_buffer_stop(void* context, FlipperFormat* ff);
+
+void meal_pager_save_settings(void* context);
+void meal_pager_read_settings(void* context);
+void meal_pager_set_max_values(void* context);

+ 131 - 0
meal_pager/helpers/retekess/meal_pager_retekess_t119.c

@@ -0,0 +1,131 @@
+
+#include "meal_pager_retekess_t119.h"
+
+static char* genRawDataT119(int zero, int one, const char* bits) {
+    int bitsLen = strlen(bits);
+    int lineLen = 256; // Adjust the line length as needed
+    char* line = (char*)malloc(lineLen * sizeof(char));
+
+    // Initialize the line with the first part
+    char* res = (char*)malloc(lineLen * sizeof(char));
+    res[0] = '\0'; // Null-terminate the result string
+
+    customConcat(res, "-6000");
+
+    // Append bits and create the line
+    for(int i = 0; i < bitsLen; i++) {
+        char c = bits[i];
+        int t = (c == '0') ? zero : one;
+
+        if(i % 2 == 0) {
+            snprintf(line, lineLen, " %d", t);
+        } else {
+            snprintf(line, lineLen, " -%d", t);
+        }
+
+        // Concatenate the line to the result string
+        customConcat(res, line);
+    }
+
+    // Append the closing part to the line
+    customConcat(res, " 200 -6000");
+
+    free(line); // Free memory allocated for the line
+
+    return res;
+}
+
+static void meal_pager_retekess_t119_generate_pager(
+    void* context,
+    char* stationId,
+    uint32_t pager,
+    FlipperFormat* ff) {
+    Meal_Pager* app = context;
+    char pagerId[11];
+    char* fullId = (char*)malloc(25 * sizeof(char));
+    uint32_t action = 0; // 0 = ring, 1 = mute
+    char actionId[2];
+    //FURI_LOG_D(TAG, "Generating T119 Data for Pager %lu", pager);
+    app->current_pager = pager;
+    meal_pager_transmit_model_set_pager(app->meal_pager_transmit, app->current_pager);
+    uint32ToBinaray(pager, pagerId, 10);
+    uint32ToBinaray(action, actionId, 1);
+    reverse(pagerId);
+    reverse(actionId);
+    //FURI_LOG_D(TAG, "Station Bin: %s", stationId);
+    //FURI_LOG_D(TAG, "Pager Bin: %s", pagerId);
+    //FURI_LOG_D(TAG, "Action Bin: %s", actionId);
+    customConcat(fullId, stationId);
+    customConcat(fullId, pagerId);
+    //FURI_LOG_D(TAG, "Result %s", fullId);
+    //FURI_LOG_D(TAG, "Station & Pager: %s", stationPagerId);
+    //FURI_LOG_D(TAG, "Station & Pager: %s", stationPagerId);
+    customConcat(fullId, actionId);
+    //FURI_LOG_D(TAG, "CustomConcat: %s", fullId);
+    //FURI_LOG_D(TAG, "Station & Pager & Action: %s", fullId);
+    char* manchester = encManchester(fullId, 0);
+    //FURI_LOG_D(TAG, "Manchester: %s", manchester);
+    char* rawSignal = genRawDataT119(200, 600, manchester);
+    //FURI_LOG_D(TAG, "RAW_Data: %s", rawSignal);
+    for(uint32_t i = 1; app->repeats >= i; i++) {
+        flipper_format_write_string_cstr(ff, "RAW_Data", rawSignal);
+    }
+    //flipper_format_write_string_cstr(ff, "RAW_Data", rawSignal);
+    free(manchester);
+    free(rawSignal);
+}
+
+static void
+    meal_pager_retekess_t119_generate_station(void* context, uint32_t station, FlipperFormat* ff) {
+    Meal_Pager* app = context;
+    FURI_LOG_D(
+        TAG,
+        "Generating T119 Data for Station %lu. Pagers From %lu to %lu",
+        station,
+        app->first_pager,
+        app->last_pager);
+    app->current_station = station;
+    app->current_pager = app->first_pager;
+    char stationId[14];
+    uint32ToBinaray(station, stationId, 13);
+    reverse(stationId);
+    meal_pager_transmit_model_set_station(app->meal_pager_transmit, app->current_station);
+    for(uint32_t i = app->current_pager; i <= app->last_pager; i++) {
+        meal_pager_retekess_t119_generate_pager(app, stationId, i, ff);
+        //furi_thread_flags_wait(0, FuriFlagWaitAny, 1);
+        if(app->stop_transmit) {
+            break;
+        }
+    }
+}
+
+bool meal_pager_retekess_t119_generate_all(void* context) {
+    Meal_Pager* app = context;
+
+    app->current_pager = 1;
+    app->current_station = app->first_station;
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+    bool success = meal_pager_save_subghz_buffer_file_start(
+        app, ff, storage, MEAL_PAGER_SUBGHZ_FILE_FREQUENCY);
+
+    if(!success) {
+        FURI_LOG_D(TAG, "failed to save to buffer");
+        meal_pager_save_subghz_buffer_stop(app, ff);
+        furi_record_close(RECORD_STORAGE);
+        return success;
+    }
+
+    for(uint32_t i = app->current_station; i <= app->last_station; i++) {
+        meal_pager_retekess_t119_generate_station(app, i, ff);
+        //furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+        if(app->stop_transmit) {
+            break;
+        }
+    }
+
+    meal_pager_save_subghz_buffer_stop(app, ff);
+    furi_record_close(RECORD_STORAGE);
+    return success;
+}

+ 8 - 0
meal_pager/helpers/retekess/meal_pager_retekess_t119.h

@@ -0,0 +1,8 @@
+
+#pragma once
+
+#include "../../meal_pager_i.h"
+#include "../meal_pager_calc.h"
+#include "../meal_pager_storage.h"
+
+bool meal_pager_retekess_t119_generate_all(void* context);

+ 116 - 0
meal_pager/helpers/retekess/meal_pager_retekess_td157.c

@@ -0,0 +1,116 @@
+
+#include "meal_pager_retekess_td157.h"
+
+static char* genRawDataTD157(int zero, int one, const char* bits) {
+    int bitsLen = strlen(bits);
+    int lineLen = 256; // Adjust the line length as needed
+    char* line = (char*)malloc(lineLen * sizeof(char));
+
+    // Initialize the line with the first part
+    char* res = (char*)malloc(lineLen * sizeof(char));
+    res[0] = '\0'; // Null-terminate the result string
+
+    customConcat(res, "-6000");
+
+    // Append bits and create the line
+    for(int i = 0; i < bitsLen; i++) {
+        char c = bits[i];
+        int t = (c == '0') ? zero : one;
+
+        if(i % 2 == 0) {
+            snprintf(line, lineLen, " %d", t);
+        } else {
+            snprintf(line, lineLen, " -%d", t);
+        }
+
+        // Concatenate the line to the result string
+        customConcat(res, line);
+    }
+
+    // Append the closing part to the line
+    customConcat(res, " 200 -6000");
+
+    free(line); // Free memory allocated for the line
+
+    return res;
+}
+
+static void meal_pager_retekess_td157_generate_pager(
+    void* context,
+    char* stationId,
+    uint32_t pager,
+    FlipperFormat* ff) {
+    Meal_Pager* app = context;
+    char pagerId[11];
+    char* fullId = (char*)malloc(25 * sizeof(char));
+    uint32_t action = 2;
+    char actionId[4];
+    app->current_pager = pager;
+    meal_pager_transmit_model_set_pager(app->meal_pager_transmit, app->current_pager);
+    FURI_LOG_D(TAG, "Generating TD157 Data for Pager %lu", pager);
+    uint32ToBinaray(pager, pagerId, 10);
+    customConcat(fullId, stationId);
+    customConcat(fullId, pagerId);
+    uint32ToBinaray(action, actionId, 4);
+    customConcat(fullId, actionId);
+    char* manchester = encManchester(fullId, 0);
+    char* rawSignal = genRawDataTD157(200, 600, manchester);
+    for(uint32_t i = 1; app->repeats >= i; i++) {
+        flipper_format_write_string_cstr(ff, "RAW_Data", rawSignal);
+    }
+    free(manchester);
+    free(rawSignal);
+}
+
+static void
+    meal_pager_retekess_td157_generate_station(void* context, uint32_t station, FlipperFormat* ff) {
+    Meal_Pager* app = context;
+    FURI_LOG_D(
+        TAG,
+        "Generating TD157 Data for Station %lu. Pagers From %lu to %lu",
+        station,
+        app->first_pager,
+        app->last_pager);
+    app->current_station = station;
+    app->current_pager = app->first_pager;
+    char stationId[14];
+    uint32ToBinaray(station, stationId, 10);
+    //reverse(stationId);
+    meal_pager_transmit_model_set_station(app->meal_pager_transmit, app->current_station);
+    for(uint32_t i = app->current_pager; i <= app->last_pager; i++) {
+        meal_pager_retekess_td157_generate_pager(app, stationId, i, ff);
+        if(app->stop_transmit) {
+            break;
+        }
+    }
+}
+
+bool meal_pager_retekess_td157_generate_all(void* context) {
+    Meal_Pager* app = context;
+
+    app->current_pager = 1;
+    app->current_station = app->first_station;
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+    bool success = meal_pager_save_subghz_buffer_file_start(
+        app, ff, storage, MEAL_PAGER_SUBGHZ_FILE_FREQUENCY);
+
+    if(!success) {
+        FURI_LOG_D(TAG, "failed to save to buffer");
+        meal_pager_save_subghz_buffer_stop(app, ff);
+        furi_record_close(RECORD_STORAGE);
+        return success;
+    }
+
+    for(uint32_t i = app->current_station; i <= app->last_station; i++) {
+        meal_pager_retekess_td157_generate_station(app, i, ff);
+        if(app->stop_transmit) {
+            break;
+        }
+    }
+
+    meal_pager_save_subghz_buffer_stop(app, ff);
+    furi_record_close(RECORD_STORAGE);
+    return success;
+}

+ 8 - 0
meal_pager/helpers/retekess/meal_pager_retekess_td157.h

@@ -0,0 +1,8 @@
+
+#pragma once
+
+#include "../../meal_pager_i.h"
+#include "../meal_pager_calc.h"
+#include "../meal_pager_storage.h"
+
+bool meal_pager_retekess_td157_generate_all(void* context);

+ 118 - 0
meal_pager/helpers/retekess/meal_pager_retekess_td165.c

@@ -0,0 +1,118 @@
+
+#include "meal_pager_retekess_td165.h"
+
+static char* genRawDataTD165(int zero, int one, const char* bits) {
+    int bitsLen = strlen(bits);
+    int lineLen = 256; // Adjust the line length as needed
+    char* line = (char*)malloc(lineLen * sizeof(char));
+
+    // Initialize the line with the first part
+    char* res = (char*)malloc(lineLen * sizeof(char));
+    res[0] = '\0'; // Null-terminate the result string
+
+    customConcat(res, "-6000");
+
+    // Append bits and create the line
+    for(int i = 0; i < bitsLen; i++) {
+        char c = bits[i];
+        int t = (c == '0') ? zero : one;
+
+        if(i % 2 == 0) {
+            snprintf(line, lineLen, " %d", t);
+        } else {
+            snprintf(line, lineLen, " -%d", t);
+        }
+
+        // Concatenate the line to the result string
+        customConcat(res, line);
+    }
+
+    // Append the closing part to the line
+    customConcat(res, " 200 -6000");
+
+    free(line); // Free memory allocated for the line
+
+    return res;
+}
+
+static void meal_pager_retekess_td165_generate_pager(
+    void* context,
+    char* stationId,
+    uint32_t pager,
+    FlipperFormat* ff) {
+    Meal_Pager* app = context;
+    char pagerId[11];
+    char* fullId = (char*)malloc(25 * sizeof(char));
+    uint32_t action = 0;
+    char actionId[2];
+    app->current_pager = pager;
+    meal_pager_transmit_model_set_pager(app->meal_pager_transmit, app->current_pager);
+    FURI_LOG_D(TAG, "Generating TD165 Data for Pager %lu", pager);
+    uint32ToBinaray(pager, pagerId, 10);
+    reverse(pagerId);
+    customConcat(fullId, stationId);
+    customConcat(fullId, pagerId);
+    uint32ToBinaray(action, actionId, 1);
+    reverse(actionId);
+    customConcat(fullId, actionId);
+    char* manchester = encManchester(fullId, 0);
+    char* rawSignal = genRawDataTD165(200, 600, manchester);
+    for(uint32_t i = 1; app->repeats >= i; i++) {
+        flipper_format_write_string_cstr(ff, "RAW_Data", rawSignal);
+    }
+    free(manchester);
+    free(rawSignal);
+}
+
+static void
+    meal_pager_retekess_td165_generate_station(void* context, uint32_t station, FlipperFormat* ff) {
+    Meal_Pager* app = context;
+    FURI_LOG_D(
+        TAG,
+        "Generating TD165 Data for Station %lu. Pagers From %lu to %lu",
+        station,
+        app->first_pager,
+        app->last_pager);
+    app->current_station = station;
+    app->current_pager = app->first_pager;
+    char stationId[14];
+    uint32ToBinaray(station, stationId, 13);
+    reverse(stationId);
+    meal_pager_transmit_model_set_station(app->meal_pager_transmit, app->current_station);
+    for(uint32_t i = app->current_pager; i <= app->last_pager; i++) {
+        meal_pager_retekess_td165_generate_pager(app, stationId, i, ff);
+        if(app->stop_transmit) {
+            break;
+        }
+    }
+}
+
+bool meal_pager_retekess_td165_generate_all(void* context) {
+    Meal_Pager* app = context;
+
+    app->current_pager = 1;
+    app->current_station = app->first_station;
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+    bool success = meal_pager_save_subghz_buffer_file_start(
+        app, ff, storage, MEAL_PAGER_SUBGHZ_FILE_FREQUENCY);
+
+    if(!success) {
+        FURI_LOG_D(TAG, "failed to save to buffer");
+        meal_pager_save_subghz_buffer_stop(app, ff);
+        furi_record_close(RECORD_STORAGE);
+        return success;
+    }
+
+    for(uint32_t i = app->current_station; i <= app->last_station; i++) {
+        meal_pager_retekess_td165_generate_station(app, i, ff);
+        if(app->stop_transmit) {
+            break;
+        }
+    }
+
+    meal_pager_save_subghz_buffer_stop(app, ff);
+    furi_record_close(RECORD_STORAGE);
+    return success;
+}

+ 8 - 0
meal_pager/helpers/retekess/meal_pager_retekess_td165.h

@@ -0,0 +1,8 @@
+
+#pragma once
+
+#include "../../meal_pager_i.h"
+#include "../meal_pager_calc.h"
+#include "../meal_pager_storage.h"
+
+bool meal_pager_retekess_td165_generate_all(void* context);

+ 122 - 0
meal_pager/helpers/retekess/meal_pager_retekess_td174.c

@@ -0,0 +1,122 @@
+
+#include "meal_pager_retekess_td174.h"
+
+static char* genRawDataTd174(int zero, int one, const char* bits) {
+    int bitsLen = strlen(bits);
+    int lineLen = 256; // Adjust the line length as needed
+    char* line = (char*)malloc(lineLen * sizeof(char));
+
+    // Initialize the line with the first part
+    char* res = (char*)malloc(lineLen * sizeof(char));
+    res[0] = '\0'; // Null-terminate the result string
+
+    customConcat(res, "-6000 300 -900"); // Always starts with 01
+
+    // Append bits and create the line
+    for(int i = 0; i < bitsLen; i++) {
+        char c = bits[i];
+        int t = (c == '0') ? zero : one;
+
+        if(i % 2 == 0) {
+            snprintf(line, lineLen, " %d", t);
+        } else {
+            snprintf(line, lineLen, " -%d", t);
+        }
+
+        // Concatenate the line to the result string
+        customConcat(res, line);
+    }
+
+    // Append the closing part to the line
+    customConcat(res, " 300 -6000");
+
+    free(line); // Free memory allocated for the line
+
+    return res;
+}
+
+static void meal_pager_retekess_td174_generate_pager(
+    void* context,
+    char* stationId,
+    uint32_t pager,
+    FlipperFormat* ff) {
+    Meal_Pager* app = context;
+    char pagerId[11];
+    char* fullId = (char*)malloc(25 * sizeof(char));
+    uint32_t action = 0; // 0 = ring, 1 = mute
+    char actionId[2];
+    //FURI_LOG_D(TAG, "Generating TD174 Data for Pager %lu", pager);
+    app->current_pager = pager;
+    meal_pager_transmit_model_set_pager(app->meal_pager_transmit, app->current_pager);
+    uint32ToBinaray(pager, pagerId, 8);
+    uint32ToBinaray(action, actionId, 2);
+    reverse(pagerId);
+    reverse(actionId);
+    //FURI_LOG_D(TAG, "Station Bin: %s", stationId);
+    //FURI_LOG_D(TAG, "Pager Bin: %s", pagerId);
+    //FURI_LOG_D(TAG, "Action Bin: %s", actionId);
+    customConcat(fullId, stationId);
+    customConcat(fullId, actionId);
+    customConcat(fullId, pagerId);
+    char* manchester = encManchester(fullId, 0);
+    char* rawSignal = genRawDataTd174(300, 900, manchester);
+    for(uint32_t i = 1; app->repeats >= i; i++) {
+        flipper_format_write_string_cstr(ff, "RAW_Data", rawSignal);
+    }
+    free(manchester);
+    free(rawSignal);
+}
+
+static void
+    meal_pager_retekess_td174_generate_station(void* context, uint32_t station, FlipperFormat* ff) {
+    Meal_Pager* app = context;
+    FURI_LOG_D(
+        TAG,
+        "Generating TD174 Data for Station %lu. Pagers From %lu to %lu",
+        station,
+        app->first_pager,
+        app->last_pager);
+    app->current_station = station;
+    app->current_pager = app->first_pager;
+    char stationId[14];
+    uint32ToBinaray(station, stationId, 13);
+    reverse(stationId);
+    meal_pager_transmit_model_set_station(app->meal_pager_transmit, app->current_station);
+    for(uint32_t i = app->current_pager; i <= app->last_pager; i++) {
+        meal_pager_retekess_td174_generate_pager(app, stationId, i, ff);
+        if(app->stop_transmit) {
+            break;
+        }
+    }
+}
+
+bool meal_pager_retekess_td174_generate_all(void* context) {
+    Meal_Pager* app = context;
+
+    app->current_pager = 1;
+    app->current_station = app->first_station;
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+    bool success = meal_pager_save_subghz_buffer_file_start(
+        app, ff, storage, MEAL_PAGER_SUBGHZ_FILE_ALT_FREQUENCY);
+
+    if(!success) {
+        FURI_LOG_D(TAG, "failed to save to buffer");
+        meal_pager_save_subghz_buffer_stop(app, ff);
+        furi_record_close(RECORD_STORAGE);
+        return success;
+    }
+
+    for(uint32_t i = app->current_station; i <= app->last_station; i++) {
+        meal_pager_retekess_td174_generate_station(app, i, ff);
+        //furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+        if(app->stop_transmit) {
+            break;
+        }
+    }
+
+    meal_pager_save_subghz_buffer_stop(app, ff);
+    furi_record_close(RECORD_STORAGE);
+    return success;
+}

+ 7 - 0
meal_pager/helpers/retekess/meal_pager_retekess_td174.h

@@ -0,0 +1,7 @@
+
+#pragma once
+
+#include "../../meal_pager_i.h"
+#include "../meal_pager_calc.h"
+
+bool meal_pager_retekess_td174_generate_all(void* context);

+ 77 - 0
meal_pager/helpers/subghz/subghz.c

@@ -0,0 +1,77 @@
+/* Reduced variant of the Flipper Zero SubGhz Class */
+
+#include "subghz_i.h"
+#include "../../helpers/meal_pager_custom_event.h"
+#include "../../helpers/meal_pager_led.h"
+//#include "../meal_pager_storage.h"
+
+SubGhz* subghz_alloc() {
+    SubGhz* subghz = malloc(sizeof(SubGhz));
+
+    subghz->file_path = furi_string_alloc();
+
+    subghz->txrx = subghz_txrx_alloc();
+
+    return subghz;
+}
+
+void subghz_free(SubGhz* subghz) {
+    //TxRx
+    subghz_txrx_free(subghz->txrx);
+
+    // Furi strings
+    furi_string_free(subghz->file_path);
+
+    // The rest
+    free(subghz);
+}
+
+void subghz_scene_transmit_callback_end_tx(void* context) {
+    furi_assert(context);
+    //UNUSED(context);
+    FURI_LOG_D(TAG, "callback end");
+    Meal_Pager* app = context;
+    view_dispatcher_send_custom_event(
+        app->view_dispatcher, Meal_PagerCustomEventViewTransmitterSendStop);
+}
+
+void subghz_send(void* context) {
+    //UNUSED(context);
+    Meal_Pager* app = context;
+    //SubGhz* subghz = subghz_alloc();
+
+    FURI_LOG_D(TAG, "loading protocol from file");
+    subghz_load_protocol_from_file(app->subghz);
+
+    /*Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* ff = flipper_format_file_alloc(storage);
+
+    if(!flipper_format_file_open_existing(ff, MEAL_PAGER_TMP_FILE)) {
+        //totp_close_config_file(fff_file);
+        FURI_LOG_E(TAG, "Error reading Temp File %s", MEAL_PAGER_TMP_FILE);
+        furi_record_close(RECORD_STORAGE);
+        return;
+    }*/
+
+    //subghz_txrx_tx_start(subghz->txrx, ff);
+
+    FURI_LOG_D(TAG, "Starting Transmission");
+    subghz_txrx_tx_start(
+        app->subghz->txrx,
+        subghz_txrx_get_fff_data(app->subghz->txrx)); //Seems like it must be done this way
+
+    FURI_LOG_D(TAG, "setting sugbhz raw file encoder worker callback");
+    subghz_txrx_set_raw_file_encoder_worker_callback_end(
+        app->subghz->txrx, subghz_scene_transmit_callback_end_tx, app);
+    app->state_notifications = SubGhzNotificationStateTx;
+
+    /*flipper_format_rewind(ff);
+    flipper_format_file_close(ff);
+    flipper_format_free(ff);
+
+    furi_record_close(RECORD_STORAGE);*/
+
+    //subghz_free(subghz);
+    FURI_LOG_D(TAG, "Finished Transmitting");
+    //meal_pager_blink_stop(app);
+}

+ 9 - 0
meal_pager/helpers/subghz/subghz.h

@@ -0,0 +1,9 @@
+#pragma once
+
+#include "subghz_i.h"
+
+typedef struct SubGhz SubGhz;
+
+SubGhz* subghz_alloc();
+void subghz_free(SubGhz* subghz);
+void subghz_send(void* context);

+ 14 - 0
meal_pager/helpers/subghz/subghz_error_type.h

@@ -0,0 +1,14 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+
+/** SubGhzErrorType */
+typedef enum {
+    SubGhzErrorTypeNoError = 0, /** There are no errors */
+    SubGhzErrorTypeParseFile =
+        1, /** File parsing error, or wrong file structure, or missing required parameters. more accurate data can be obtained through the debug port */
+    SubGhzErrorTypeOnlyRX =
+        2, /** Transmission on this frequency is blocked by regional settings */
+    SubGhzErrorTypeParserOthers = 3, /** Error in protocol parameters description */
+} SubGhzErrorType;

+ 260 - 0
meal_pager/helpers/subghz/subghz_i.c

@@ -0,0 +1,260 @@
+#include "subghz_i.h"
+
+#include "subghz/types.h"
+#include <math.h>
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+#include <flipper_format/flipper_format.h>
+
+#include <flipper_format/flipper_format_i.h>
+#include <lib/toolbox/stream/stream.h>
+#include <lib/subghz/protocols/raw.h>
+
+//#define TAG "SubGhz"
+/*
+void subghz_set_default_preset(SubGhz* subghz) {
+    furi_assert(subghz);
+    subghz_txrx_set_preset(
+        subghz->txrx,
+        "AM650",
+        subghz_setting_get_default_frequency(subghz_txrx_get_setting(subghz->txrx)),
+        NULL,
+        0);
+}*/
+
+/*bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format) {
+    switch(subghz_txrx_tx_start(subghz->txrx, flipper_format)) {
+    case SubGhzTxRxStartTxStateErrorParserOthers:
+        dialog_message_show_storage_error(
+            subghz->dialogs, "Error in protocol\nparameters\ndescription");
+        break;
+    case SubGhzTxRxStartTxStateErrorOnlyRx:
+        FURI_LOG_D(TAG, 'Cannot send, only RX possible');
+        break;
+
+    default:
+        return true;
+        break;
+    }
+    return false;
+}*/
+
+bool subghz_key_load(SubGhz* subghz, const char* file_path) { //, bool show_dialog) {
+    furi_assert(subghz);
+    furi_assert(file_path);
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    FlipperFormat* fff_data_file = flipper_format_file_alloc(storage);
+    Stream* fff_data_stream =
+        flipper_format_get_raw_stream(subghz_txrx_get_fff_data(subghz->txrx));
+
+    SubGhzLoadKeyState load_key_state = SubGhzLoadKeyStateParseErr;
+    FuriString* temp_str = furi_string_alloc();
+    uint32_t temp_data32;
+
+    do {
+        stream_clean(fff_data_stream);
+        if(!flipper_format_file_open_existing(fff_data_file, file_path)) {
+            FURI_LOG_E(TAG, "Error open file %s", file_path);
+            break;
+        }
+
+        if(!flipper_format_read_header(fff_data_file, temp_str, &temp_data32)) {
+            FURI_LOG_E(TAG, "Missing or incorrect header");
+            break;
+        }
+
+        if(((!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_KEY_FILE_TYPE)) ||
+            (!strcmp(furi_string_get_cstr(temp_str), SUBGHZ_RAW_FILE_TYPE))) &&
+           temp_data32 == SUBGHZ_KEY_FILE_VERSION) {
+        } else {
+            FURI_LOG_E(TAG, "Type or version mismatch");
+            break;
+        }
+
+        //Load frequency
+        if(!flipper_format_read_uint32(fff_data_file, "Frequency", &temp_data32, 1)) {
+            FURI_LOG_E(TAG, "Missing Frequency");
+            break;
+        }
+
+        if(!subghz_txrx_radio_device_is_frequecy_valid(subghz->txrx, temp_data32)) {
+            FURI_LOG_E(TAG, "Frequency not supported");
+            break;
+        }
+
+        //Load preset
+        if(!flipper_format_read_string(fff_data_file, "Preset", temp_str)) {
+            FURI_LOG_E(TAG, "Missing Preset");
+            break;
+        }
+
+        furi_string_set_str(
+            temp_str, subghz_txrx_get_preset_name(subghz->txrx, furi_string_get_cstr(temp_str)));
+        if(!strcmp(furi_string_get_cstr(temp_str), "")) {
+            break;
+        }
+        SubGhzSetting* setting = subghz_txrx_get_setting(subghz->txrx);
+
+        if(!strcmp(furi_string_get_cstr(temp_str), "CUSTOM")) {
+            //TODO FL-3551: add Custom_preset_module
+            //delete preset if it already exists
+            subghz_setting_delete_custom_preset(setting, furi_string_get_cstr(temp_str));
+            //load custom preset from file
+            if(!subghz_setting_load_custom_preset(
+                   setting, furi_string_get_cstr(temp_str), fff_data_file)) {
+                FURI_LOG_E(TAG, "Missing Custom preset");
+                break;
+            }
+        }
+        size_t preset_index =
+            subghz_setting_get_inx_preset_by_name(setting, furi_string_get_cstr(temp_str));
+        subghz_txrx_set_preset(
+            subghz->txrx,
+            furi_string_get_cstr(temp_str),
+            temp_data32,
+            subghz_setting_get_preset_data(setting, preset_index),
+            subghz_setting_get_preset_data_size(setting, preset_index));
+
+        //Load protocol
+        if(!flipper_format_read_string(fff_data_file, "Protocol", temp_str)) {
+            FURI_LOG_E(TAG, "Missing Protocol");
+            break;
+        }
+
+        FlipperFormat* fff_data = subghz_txrx_get_fff_data(subghz->txrx);
+        if(!strcmp(furi_string_get_cstr(temp_str), "RAW")) {
+            //if RAW
+            subghz->load_type_file = SubGhzLoadTypeFileRaw;
+            subghz_protocol_raw_gen_fff_data(
+                fff_data, file_path, subghz_txrx_radio_device_get_name(subghz->txrx));
+        } else {
+            subghz->load_type_file = SubGhzLoadTypeFileKey;
+            stream_copy_full(
+                flipper_format_get_raw_stream(fff_data_file),
+                flipper_format_get_raw_stream(fff_data));
+        }
+
+        if(subghz_txrx_load_decoder_by_name_protocol(
+               subghz->txrx, furi_string_get_cstr(temp_str))) {
+            SubGhzProtocolStatus status = subghz_protocol_decoder_base_deserialize(
+                subghz_txrx_get_decoder(subghz->txrx), fff_data);
+            if(status != SubGhzProtocolStatusOk) {
+                load_key_state = SubGhzLoadKeyStateProtocolDescriptionErr;
+                break;
+            }
+        } else {
+            FURI_LOG_E(TAG, "Protocol not found");
+            break;
+        }
+
+        load_key_state = SubGhzLoadKeyStateOK;
+    } while(0);
+
+    furi_string_free(temp_str);
+    flipper_format_free(fff_data_file);
+    furi_record_close(RECORD_STORAGE);
+
+    switch(load_key_state) {
+    case SubGhzLoadKeyStateParseErr:
+        //if(show_dialog) {
+        //    dialog_message_show_storage_error(subghz->dialogs, "Cannot parse\nfile");
+        //}
+        return false;
+    case SubGhzLoadKeyStateProtocolDescriptionErr:
+        //if(show_dialog) {
+        //    dialog_message_show_storage_error(
+        //        subghz->dialogs, "Error in protocol\nparameters\ndescription");
+        //}
+        return false;
+
+    case SubGhzLoadKeyStateOK:
+        return true;
+
+    default:
+        furi_crash("SubGhz: Unknown load_key_state.");
+        return false;
+    }
+    return false;
+}
+
+/*SubGhzLoadTypeFile subghz_get_load_type_file(SubGhz* subghz) {
+    furi_assert(subghz);
+    return subghz->load_type_file;
+}*/
+
+bool subghz_load_protocol_from_file(SubGhz* subghz) {
+    furi_assert(subghz);
+
+    //FuriString* file_path = furi_string_alloc();
+
+    bool res = false;
+
+    /*DialogsFileBrowserOptions browser_options;
+    dialog_file_browser_set_basic_options(
+        &browser_options, SUBGHZ_APP_FILENAME_EXTENSION, &I_sub1_10px);
+    browser_options.base_path = SUBGHZ_APP_FOLDER;
+
+    // Input events and views are managed by file_select
+    bool res = dialog_file_browser_show(
+        subghz->dialogs, subghz->file_path, subghz->file_path, &browser_options);
+
+    if(res) {*/
+    //res = subghz_key_load(subghz, furi_string_get_cstr(subghz->file_path), true);
+    res = subghz_key_load(subghz, MEAL_PAGER_TMP_FILE); //, true);
+    //}
+
+    //furi_string_free(file_path);
+
+    return res;
+}
+
+/*bool subghz_file_available(SubGhz* subghz) {
+    furi_assert(subghz);
+    bool ret = true;
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+
+    FS_Error fs_result =
+        storage_common_stat(storage, furi_string_get_cstr(subghz->file_path), NULL);
+
+    if(fs_result != FSE_OK) {
+        dialog_message_show_storage_error(subghz->dialogs, "File not available\n file/directory");
+        ret = false;
+    }
+
+    furi_record_close(RECORD_STORAGE);
+    return ret;
+}*/
+
+/*bool subghz_path_is_file(FuriString* path) {
+    return furi_string_end_with(path, SUBGHZ_APP_FILENAME_EXTENSION);
+}*/
+
+/*void subghz_lock(SubGhz* subghz) {
+    furi_assert(subghz);
+    subghz->lock = SubGhzLockOn;
+}*/
+
+/*void subghz_unlock(SubGhz* subghz) {
+    furi_assert(subghz);
+    subghz->lock = SubGhzLockOff;
+}*/
+
+/*bool subghz_is_locked(SubGhz* subghz) {
+    furi_assert(subghz);
+    return (subghz->lock == SubGhzLockOn);
+}*/
+
+/*void subghz_rx_key_state_set(SubGhz* subghz, SubGhzRxKeyState state) {
+    furi_assert(subghz);
+    subghz->rx_key_state = state;
+}*/
+
+/*SubGhzRxKeyState subghz_rx_key_state_get(SubGhz* subghz) {
+    furi_assert(subghz);
+    return subghz->rx_key_state;
+}*/

+ 83 - 0
meal_pager/helpers/subghz/subghz_i.h

@@ -0,0 +1,83 @@
+#pragma once
+
+#include "subghz_types.h"
+#include "subghz_error_type.h"
+#include <lib/subghz/types.h>
+#include "subghz.h"
+#include "../meal_pager_storage.h"
+
+/*#include "views/receiver.h"
+#include "views/transmitter.h"
+#include "views/subghz_frequency_analyzer.h"
+#include "views/subghz_read_raw.h"
+
+#include <gui/gui.h>
+#include <dialogs/dialogs.h>
+#include <gui/scene_manager.h>
+#include <notification/notification_messages.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/popup.h>
+#include <gui/modules/text_input.h>
+#include <gui/modules/widget.h>
+
+#include <subghz/scenes/subghz_scene.h>
+
+#include "subghz_history.h"
+
+#include <gui/modules/variable_item_list.h>
+#include <lib/toolbox/path.h>
+
+#include "rpc/rpc_app.h"
+
+#include "helpers/subghz_threshold_rssi.h"
+
+
+*/
+#include "subghz_txrx.h"
+
+#define SUBGHZ_MAX_LEN_NAME 64
+
+typedef struct SubGhz SubGhz;
+
+struct SubGhz {
+    SubGhzTxRx* txrx;
+    FuriString* file_path;
+    //FuriString* file_path_tmp;
+    //char file_name_tmp[SUBGHZ_MAX_LEN_NAME]; // just left it in to make the object not empty
+    //SubGhzNotificationState state_notifications;
+
+    /*SubGhzViewReceiver* subghz_receiver;
+    SubGhzViewTransmitter* subghz_transmitter;
+
+    SubGhzFrequencyAnalyzer* subghz_frequency_analyzer;
+    SubGhzReadRAW* subghz_read_raw;*/
+
+    //SubGhzProtocolFlag filter;
+    //FuriString* error_str;
+    //SubGhzLock lock;
+    //SubGhzThresholdRssi* threshold_rssi;
+    //SubGhzRxKeyState rx_key_state;
+    //SubGhzHistory* history;
+    SubGhzLoadTypeFile load_type_file;
+    //void* rpc_ctx;
+};
+
+//void subghz_set_default_preset(SubGhz* subghz);
+//void subghz_blink_start(SubGhz* subghz);
+//void subghz_blink_stop(SubGhz* subghz);
+
+//bool subghz_tx_start(SubGhz* subghz, FlipperFormat* flipper_format);
+//void subghz_dialog_message_show_only_rx(SubGhz* subghz);
+
+bool subghz_key_load(SubGhz* subghz, const char* file_path); //, bool show_dialog);
+bool subghz_load_protocol_from_file(SubGhz* subghz);
+//bool subghz_file_available(SubGhz* subghz);
+//SubGhzLoadTypeFile subghz_get_load_type_file(SubGhz* subghz);
+
+//void subghz_lock(SubGhz* subghz);
+//void subghz_unlock(SubGhz* subghz);
+//bool subghz_is_locked(SubGhz* subghz);
+
+//void subghz_rx_key_state_set(SubGhz* subghz, SubGhzRxKeyState state);
+//SubGhzRxKeyState subghz_rx_key_state_get(SubGhz* subghz);

+ 630 - 0
meal_pager/helpers/subghz/subghz_txrx.c

@@ -0,0 +1,630 @@
+#include "subghz_txrx_i.h"
+
+#include <lib/subghz/subghz_protocol_registry.h>
+#include <applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h>
+#include <lib/subghz/devices/cc1101_int/cc1101_int_interconnect.h>
+
+#define TAG "SubGhz"
+
+static void subghz_txrx_radio_device_power_on(SubGhzTxRx* instance) {
+    UNUSED(instance);
+    uint8_t attempts = 5;
+    while(--attempts > 0) {
+        if(furi_hal_power_enable_otg()) break;
+    }
+    if(attempts == 0) {
+        if(furi_hal_power_get_usb_voltage() < 4.5f) {
+            FURI_LOG_E(
+                TAG,
+                "Error power otg enable. BQ2589 check otg fault = %d",
+                furi_hal_power_check_otg_fault() ? 1 : 0);
+        }
+    }
+}
+
+static void subghz_txrx_radio_device_power_off(SubGhzTxRx* instance) {
+    UNUSED(instance);
+    if(furi_hal_power_is_otg_enabled()) furi_hal_power_disable_otg();
+}
+
+SubGhzTxRx* subghz_txrx_alloc() {
+    SubGhzTxRx* instance = malloc(sizeof(SubGhzTxRx));
+    instance->setting = subghz_setting_alloc();
+    subghz_setting_load(instance->setting, EXT_PATH("subghz/assets/setting_user"));
+
+    instance->preset = malloc(sizeof(SubGhzRadioPreset));
+    instance->preset->name = furi_string_alloc();
+    subghz_txrx_set_preset(
+        instance, "AM650", subghz_setting_get_default_frequency(instance->setting), NULL, 0);
+
+    instance->txrx_state = SubGhzTxRxStateSleep;
+
+    subghz_txrx_hopper_set_state(instance, SubGhzHopperStateOFF);
+    subghz_txrx_speaker_set_state(instance, SubGhzSpeakerStateDisable);
+
+    instance->worker = subghz_worker_alloc();
+    instance->fff_data = flipper_format_string_alloc();
+
+    instance->environment = subghz_environment_alloc();
+    instance->is_database_loaded =
+        subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_NAME);
+    subghz_environment_load_keystore(instance->environment, SUBGHZ_KEYSTORE_DIR_USER_NAME);
+    subghz_environment_set_came_atomo_rainbow_table_file_name(
+        instance->environment, SUBGHZ_CAME_ATOMO_DIR_NAME);
+    subghz_environment_set_alutech_at_4n_rainbow_table_file_name(
+        instance->environment, SUBGHZ_ALUTECH_AT_4N_DIR_NAME);
+    subghz_environment_set_nice_flor_s_rainbow_table_file_name(
+        instance->environment, SUBGHZ_NICE_FLOR_S_DIR_NAME);
+    subghz_environment_set_protocol_registry(
+        instance->environment, (void*)&subghz_protocol_registry);
+    instance->receiver = subghz_receiver_alloc_init(instance->environment);
+
+    subghz_worker_set_overrun_callback(
+        instance->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset);
+    subghz_worker_set_pair_callback(
+        instance->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
+    subghz_worker_set_context(instance->worker, instance->receiver);
+
+    //set default device External
+    subghz_devices_init();
+    instance->radio_device_type = SubGhzRadioDeviceTypeInternal;
+    instance->radio_device_type =
+        subghz_txrx_radio_device_set(instance, SubGhzRadioDeviceTypeExternalCC1101);
+
+    FURI_LOG_D(TAG, "completed TXRX alloc");
+
+    return instance;
+}
+
+void subghz_txrx_free(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    FURI_LOG_D(TAG, "freeing TXRX");
+
+    if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) {
+        subghz_txrx_radio_device_power_off(instance);
+        subghz_devices_end(instance->radio_device);
+    }
+
+    subghz_devices_deinit();
+
+    subghz_worker_free(instance->worker);
+    subghz_receiver_free(instance->receiver);
+    subghz_environment_free(instance->environment);
+    flipper_format_free(instance->fff_data);
+    furi_string_free(instance->preset->name);
+    subghz_setting_free(instance->setting);
+
+    free(instance->preset);
+    free(instance);
+}
+
+/*bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->is_database_loaded;
+}*/
+
+void subghz_txrx_set_preset(
+    SubGhzTxRx* instance,
+    const char* preset_name,
+    uint32_t frequency,
+    uint8_t* preset_data,
+    size_t preset_data_size) {
+    furi_assert(instance);
+    furi_string_set(instance->preset->name, preset_name);
+    SubGhzRadioPreset* preset = instance->preset;
+    preset->frequency = frequency;
+    preset->data = preset_data;
+    preset->data_size = preset_data_size;
+}
+
+const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset) {
+    UNUSED(instance);
+    const char* preset_name = "";
+    if(!strcmp(preset, "FuriHalSubGhzPresetOok270Async")) {
+        preset_name = "AM270";
+    } else if(!strcmp(preset, "FuriHalSubGhzPresetOok650Async")) {
+        preset_name = "AM650";
+    } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev238Async")) {
+        preset_name = "FM238";
+    } else if(!strcmp(preset, "FuriHalSubGhzPreset2FSKDev476Async")) {
+        preset_name = "FM476";
+    } else if(!strcmp(preset, "FuriHalSubGhzPresetCustom")) {
+        preset_name = "CUSTOM";
+    } else {
+        FURI_LOG_E(TAG, "Unknown preset");
+    }
+    return preset_name;
+}
+
+/*SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return *instance->preset;
+}*/
+
+/*void subghz_txrx_get_frequency_and_modulation(
+    SubGhzTxRx* instance,
+    FuriString* frequency,
+    FuriString* modulation) {
+    furi_assert(instance);
+    SubGhzRadioPreset* preset = instance->preset;
+    if(frequency != NULL) {
+        furi_string_printf(
+            frequency,
+            "%03ld.%02ld",
+            preset->frequency / 1000000 % 1000,
+            preset->frequency / 10000 % 100);
+    }
+    if(modulation != NULL) {
+        furi_string_printf(modulation, "%.2s", furi_string_get_cstr(preset->name));
+    }
+}*/
+
+static void subghz_txrx_begin(SubGhzTxRx* instance, uint8_t* preset_data) {
+    furi_assert(instance);
+    subghz_devices_reset(instance->radio_device);
+    subghz_devices_idle(instance->radio_device);
+    subghz_devices_load_preset(instance->radio_device, FuriHalSubGhzPresetCustom, preset_data);
+    instance->txrx_state = SubGhzTxRxStateIDLE;
+    FURI_LOG_D(TAG, "completed subghz_txrx_begin");
+}
+
+/*static uint32_t subghz_txrx_rx(SubGhzTxRx* instance, uint32_t frequency) {
+    furi_assert(instance);
+
+    furi_assert(
+        instance->txrx_state != SubGhzTxRxStateRx && instance->txrx_state != SubGhzTxRxStateSleep);
+
+    subghz_devices_idle(instance->radio_device);
+
+    uint32_t value = subghz_devices_set_frequency(instance->radio_device, frequency);
+    subghz_devices_flush_rx(instance->radio_device);
+    subghz_txrx_speaker_on(instance);
+
+    subghz_devices_start_async_rx(
+        instance->radio_device, subghz_worker_rx_callback, instance->worker);
+    subghz_worker_start(instance->worker);
+    instance->txrx_state = SubGhzTxRxStateRx;
+    return value;
+}*/
+
+static void subghz_txrx_idle(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->txrx_state != SubGhzTxRxStateSleep) {
+        subghz_devices_idle(instance->radio_device);
+        subghz_txrx_speaker_off(instance);
+        instance->txrx_state = SubGhzTxRxStateIDLE;
+    }
+    FURI_LOG_D(TAG, "completed subghz_txrx_idle");
+}
+
+/*static void subghz_txrx_rx_end(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    furi_assert(instance->txrx_state == SubGhzTxRxStateRx);
+
+    if(subghz_worker_is_running(instance->worker)) {
+        subghz_worker_stop(instance->worker);
+        subghz_devices_stop_async_rx(instance->radio_device);
+    }
+    subghz_devices_idle(instance->radio_device);
+    subghz_txrx_speaker_off(instance);
+    instance->txrx_state = SubGhzTxRxStateIDLE;
+}*/
+
+/*void subghz_txrx_sleep(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    subghz_devices_sleep(instance->radio_device);
+    instance->txrx_state = SubGhzTxRxStateSleep;
+}*/
+
+static bool subghz_txrx_tx(SubGhzTxRx* instance, uint32_t frequency) {
+    furi_assert(instance);
+    furi_assert(instance->txrx_state != SubGhzTxRxStateSleep);
+    subghz_devices_idle(instance->radio_device);
+    subghz_devices_set_frequency(instance->radio_device, frequency);
+
+    bool ret = subghz_devices_set_tx(instance->radio_device);
+    if(ret) {
+        subghz_txrx_speaker_on(instance);
+        instance->txrx_state = SubGhzTxRxStateTx;
+    }
+
+    FURI_LOG_D(TAG, "completed subghz_txrx_tx");
+    return ret;
+}
+
+SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format) {
+    furi_assert(instance);
+    furi_assert(flipper_format);
+
+    subghz_txrx_stop(instance);
+
+    SubGhzTxRxStartTxState ret = SubGhzTxRxStartTxStateErrorParserOthers;
+    FuriString* temp_str = furi_string_alloc();
+    uint32_t repeat = 200;
+
+    FURI_LOG_D(TAG, "starting loop in subghz_txrx_tx_start");
+    do {
+        FURI_LOG_D(TAG, "looping");
+        if(!flipper_format_rewind(flipper_format)) {
+            FURI_LOG_E(TAG, "Rewind error");
+            break;
+        }
+        if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
+            FURI_LOG_E(TAG, "Missing Protocol");
+            break;
+        }
+        if(!flipper_format_insert_or_update_uint32(flipper_format, "Repeat", &repeat, 1)) {
+            FURI_LOG_E(TAG, "Unable Repeat");
+            break;
+        }
+        //FURI_LOG_D(TAG, "File loaded");
+        ret = SubGhzTxRxStartTxStateOk;
+
+        SubGhzRadioPreset* preset = instance->preset;
+        instance->transmitter =
+            subghz_transmitter_alloc_init(instance->environment, furi_string_get_cstr(temp_str));
+
+        if(instance->transmitter) {
+            FURI_LOG_D(TAG, "transmitter found");
+            if(subghz_transmitter_deserialize(instance->transmitter, flipper_format) ==
+               SubGhzProtocolStatusOk) {
+                //if (false) {
+                FURI_LOG_D(TAG, "deserialization");
+                if(strcmp(furi_string_get_cstr(preset->name), "") != 0) {
+                    FURI_LOG_D(TAG, "got preset name");
+                    subghz_txrx_begin(
+                        instance,
+                        subghz_setting_get_preset_data_by_name(
+                            instance->setting, furi_string_get_cstr(preset->name)));
+                    FURI_LOG_D(TAG, "loaded preset by name");
+                    if(preset->frequency) {
+                        if(!subghz_txrx_tx(instance, preset->frequency)) {
+                            FURI_LOG_E(TAG, "Only Rx");
+                            ret = SubGhzTxRxStartTxStateErrorOnlyRx;
+                        }
+                        FURI_LOG_D(TAG, "got frequency");
+                    } else {
+                        ret = SubGhzTxRxStartTxStateErrorParserOthers;
+                    }
+
+                } else {
+                    FURI_LOG_E(
+                        TAG, "Unknown name preset \" %s \"", furi_string_get_cstr(preset->name));
+                    ret = SubGhzTxRxStartTxStateErrorParserOthers;
+                }
+
+                if(ret == SubGhzTxRxStartTxStateOk) {
+                    //Start TX
+                    FURI_LOG_D(TAG, "starting Async TX");
+                    subghz_devices_start_async_tx(
+                        instance->radio_device, subghz_transmitter_yield, instance->transmitter);
+                }
+            } else {
+                FURI_LOG_D(TAG, "no deserialization");
+                ret = SubGhzTxRxStartTxStateErrorParserOthers;
+            }
+        } else {
+            ret = SubGhzTxRxStartTxStateErrorParserOthers;
+        }
+        if(ret != SubGhzTxRxStartTxStateOk) {
+            FURI_LOG_D(TAG, "state not ok");
+            subghz_transmitter_free(instance->transmitter); // Crashes here
+            if(instance->txrx_state != SubGhzTxRxStateIDLE) {
+                subghz_txrx_idle(instance);
+            }
+        }
+    } while(false);
+    furi_string_free(temp_str);
+    return ret;
+}
+
+/*void subghz_txrx_rx_start(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    subghz_txrx_stop(instance);
+    subghz_txrx_begin(
+        instance,
+        subghz_setting_get_preset_data_by_name(
+            subghz_txrx_get_setting(instance), furi_string_get_cstr(instance->preset->name)));
+    subghz_txrx_rx(instance, instance->preset->frequency);
+}*/
+
+/*void subghz_txrx_set_need_save_callback(
+    SubGhzTxRx* instance,
+    SubGhzTxRxNeedSaveCallback callback,
+    void* context) {
+    furi_assert(instance);
+    instance->need_save_callback = callback;
+    instance->need_save_context = context;
+}*/
+
+static void subghz_txrx_tx_stop(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    furi_assert(instance->txrx_state == SubGhzTxRxStateTx);
+    //Stop TX
+    subghz_devices_stop_async_tx(instance->radio_device);
+    subghz_transmitter_stop(instance->transmitter);
+    subghz_transmitter_free(instance->transmitter);
+
+    //if protocol dynamic then we save the last upload
+    /*if(instance->decoder_result->protocol->type == SubGhzProtocolTypeDynamic) {
+        if(instance->need_save_callback) {
+            instance->need_save_callback(instance->need_save_context);
+        }
+    }*/
+    subghz_txrx_idle(instance);
+    subghz_txrx_speaker_off(instance);
+}
+
+FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->fff_data;
+}
+
+SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->setting;
+}
+
+void subghz_txrx_stop(SubGhzTxRx* instance) {
+    furi_assert(instance);
+
+    switch(instance->txrx_state) {
+    case SubGhzTxRxStateTx:
+        subghz_txrx_tx_stop(instance);
+        subghz_txrx_speaker_unmute(instance);
+        break;
+    case SubGhzTxRxStateRx:
+        //subghz_txrx_rx_end(instance);
+        //subghz_txrx_speaker_mute(instance);
+        break;
+
+    default:
+        break;
+    }
+}
+
+/*void subghz_txrx_hopper_update(SubGhzTxRx* instance) {
+    furi_assert(instance);
+
+    switch(instance->hopper_state) {
+    case SubGhzHopperStateOFF:
+    case SubGhzHopperStatePause:
+        return;
+    case SubGhzHopperStateRSSITimeOut:
+        if(instance->hopper_timeout != 0) {
+            instance->hopper_timeout--;
+            return;
+        }
+        break;
+    default:
+        break;
+    }
+    float rssi = -127.0f;
+    if(instance->hopper_state != SubGhzHopperStateRSSITimeOut) {
+        // See RSSI Calculation timings in CC1101 17.3 RSSI
+        rssi = subghz_devices_get_rssi(instance->radio_device);
+
+        // Stay if RSSI is high enough
+        if(rssi > -90.0f) {
+            instance->hopper_timeout = 10;
+            instance->hopper_state = SubGhzHopperStateRSSITimeOut;
+            return;
+        }
+    } else {
+        instance->hopper_state = SubGhzHopperStateRunnig;
+    }
+    // Select next frequency
+    if(instance->hopper_idx_frequency <
+       subghz_setting_get_hopper_frequency_count(instance->setting) - 1) {
+        instance->hopper_idx_frequency++;
+    } else {
+        instance->hopper_idx_frequency = 0;
+    }
+
+    if(instance->txrx_state == SubGhzTxRxStateRx) {
+        subghz_txrx_rx_end(instance);
+    };
+    if(instance->txrx_state == SubGhzTxRxStateIDLE) {
+        subghz_receiver_reset(instance->receiver);
+        instance->preset->frequency =
+            subghz_setting_get_hopper_frequency(instance->setting, instance->hopper_idx_frequency);
+        subghz_txrx_rx(instance, instance->preset->frequency);
+    }
+}*/
+
+/*SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->hopper_state;
+}*/
+
+void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state) {
+    furi_assert(instance);
+    instance->hopper_state = state;
+}
+
+/*void subghz_txrx_hopper_unpause(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->hopper_state == SubGhzHopperStatePause) {
+        instance->hopper_state = SubGhzHopperStateRunnig;
+    }
+}*/
+
+/*void subghz_txrx_hopper_pause(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->hopper_state == SubGhzHopperStateRunnig) {
+        instance->hopper_state = SubGhzHopperStatePause;
+    }
+}*/
+
+void subghz_txrx_speaker_on(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->speaker_state == SubGhzSpeakerStateEnable) {
+        if(furi_hal_speaker_acquire(30)) {
+            subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker);
+        } else {
+            instance->speaker_state = SubGhzSpeakerStateDisable;
+        }
+    }
+}
+
+void subghz_txrx_speaker_off(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->speaker_state != SubGhzSpeakerStateDisable) {
+        if(furi_hal_speaker_is_mine()) {
+            subghz_devices_set_async_mirror_pin(instance->radio_device, NULL);
+            furi_hal_speaker_release();
+            if(instance->speaker_state == SubGhzSpeakerStateShutdown)
+                instance->speaker_state = SubGhzSpeakerStateDisable;
+        }
+    }
+}
+
+/*void subghz_txrx_speaker_mute(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->speaker_state == SubGhzSpeakerStateEnable) {
+        if(furi_hal_speaker_is_mine()) {
+            subghz_devices_set_async_mirror_pin(instance->radio_device, NULL);
+        }
+    }
+}*/
+
+void subghz_txrx_speaker_unmute(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    if(instance->speaker_state == SubGhzSpeakerStateEnable) {
+        if(furi_hal_speaker_is_mine()) {
+            subghz_devices_set_async_mirror_pin(instance->radio_device, &gpio_speaker);
+        }
+    }
+}
+
+void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state) {
+    furi_assert(instance);
+    instance->speaker_state = state;
+}
+
+/*SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->speaker_state;
+}*/
+
+bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol) {
+    furi_assert(instance);
+    furi_assert(name_protocol);
+    bool res = false;
+    instance->decoder_result =
+        subghz_receiver_search_decoder_base_by_name(instance->receiver, name_protocol);
+    if(instance->decoder_result) {
+        res = true;
+    }
+    return res;
+}
+
+SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->decoder_result;
+}
+
+/*bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return (
+        (instance->decoder_result->protocol->flag & SubGhzProtocolFlag_Save) ==
+        SubGhzProtocolFlag_Save);
+}*/
+
+/*bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type) {
+    furi_assert(instance);
+    const SubGhzProtocol* protocol = instance->decoder_result->protocol;
+    if(check_type) {
+        return (
+            ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) &&
+            protocol->encoder->deserialize && protocol->type == SubGhzProtocolTypeStatic);
+    }
+    return (
+        ((protocol->flag & SubGhzProtocolFlag_Send) == SubGhzProtocolFlag_Send) &&
+        protocol->encoder->deserialize);
+}*/
+
+/*void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter) {
+    furi_assert(instance);
+    subghz_receiver_set_filter(instance->receiver, filter);
+}*/
+
+/*void subghz_txrx_set_rx_calback(
+    SubGhzTxRx* instance,
+    SubGhzReceiverCallback callback,
+    void* context) {
+    subghz_receiver_set_rx_callback(instance->receiver, callback, context);
+}*/
+
+void subghz_txrx_set_raw_file_encoder_worker_callback_end(
+    SubGhzTxRx* instance,
+    SubGhzProtocolEncoderRAWCallbackEnd callback,
+    void* context) {
+    subghz_protocol_raw_file_encoder_worker_set_callback_end(
+        (SubGhzProtocolEncoderRAW*)subghz_transmitter_get_protocol_instance(instance->transmitter),
+        callback,
+        context);
+}
+
+bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name) {
+    furi_assert(instance);
+
+    bool is_connect = false;
+    bool is_otg_enabled = furi_hal_power_is_otg_enabled();
+
+    if(!is_otg_enabled) {
+        subghz_txrx_radio_device_power_on(instance);
+    }
+
+    const SubGhzDevice* device = subghz_devices_get_by_name(name);
+    if(device) {
+        is_connect = subghz_devices_is_connect(device);
+    }
+
+    if(!is_otg_enabled) {
+        subghz_txrx_radio_device_power_off(instance);
+    }
+    return is_connect;
+}
+
+SubGhzRadioDeviceType
+    subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type) {
+    furi_assert(instance);
+
+    if(radio_device_type == SubGhzRadioDeviceTypeExternalCC1101 &&
+       subghz_txrx_radio_device_is_external_connected(instance, SUBGHZ_DEVICE_CC1101_EXT_NAME)) {
+        subghz_txrx_radio_device_power_on(instance);
+        instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_EXT_NAME);
+        subghz_devices_begin(instance->radio_device);
+        instance->radio_device_type = SubGhzRadioDeviceTypeExternalCC1101;
+    } else {
+        subghz_txrx_radio_device_power_off(instance);
+        if(instance->radio_device_type != SubGhzRadioDeviceTypeInternal) {
+            subghz_devices_end(instance->radio_device);
+        }
+        instance->radio_device = subghz_devices_get_by_name(SUBGHZ_DEVICE_CC1101_INT_NAME);
+        instance->radio_device_type = SubGhzRadioDeviceTypeInternal;
+    }
+
+    return instance->radio_device_type;
+}
+
+/*SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return instance->radio_device_type;
+}*/
+
+/*float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return subghz_devices_get_rssi(instance->radio_device);
+}*/
+
+const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance) {
+    furi_assert(instance);
+    return subghz_devices_get_name(instance->radio_device);
+}
+
+bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency) {
+    furi_assert(instance);
+    return subghz_devices_is_frequency_valid(instance->radio_device, frequency);
+}

+ 336 - 0
meal_pager/helpers/subghz/subghz_txrx.h

@@ -0,0 +1,336 @@
+#pragma once
+
+#include "subghz_types.h"
+
+#include <lib/subghz/subghz_worker.h>
+#include <lib/subghz/subghz_setting.h>
+#include <lib/subghz/receiver.h>
+#include <lib/subghz/transmitter.h>
+#include <lib/subghz/protocols/raw.h>
+#include <lib/subghz/devices/devices.h>
+
+typedef struct SubGhzTxRx SubGhzTxRx;
+
+typedef void (*SubGhzTxRxNeedSaveCallback)(void* context);
+
+typedef enum {
+    SubGhzTxRxStartTxStateOk,
+    SubGhzTxRxStartTxStateErrorOnlyRx,
+    SubGhzTxRxStartTxStateErrorParserOthers,
+} SubGhzTxRxStartTxState;
+
+/**
+ * Allocate SubGhzTxRx
+ * 
+ * @return SubGhzTxRx* pointer to SubGhzTxRx
+ */
+SubGhzTxRx* subghz_txrx_alloc();
+
+/**
+ * Free SubGhzTxRx
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+void subghz_txrx_free(SubGhzTxRx* instance);
+
+/**
+ * Check if the database is loaded
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return bool True if the database is loaded
+ */
+//bool subghz_txrx_is_database_loaded(SubGhzTxRx* instance);
+
+/**
+ * Set preset 
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param preset_name Name of preset
+ * @param frequency Frequency in Hz
+ * @param preset_data Data of preset
+ * @param preset_data_size Size of preset data
+ */
+void subghz_txrx_set_preset(
+    SubGhzTxRx* instance,
+    const char* preset_name,
+    uint32_t frequency,
+    uint8_t* preset_data,
+    size_t preset_data_size);
+
+/**
+ * Get name of preset
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param preset String of preset 
+ * @return const char*  Name of preset
+ */
+const char* subghz_txrx_get_preset_name(SubGhzTxRx* instance, const char* preset);
+
+/**
+ * Get of preset
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return SubGhzRadioPreset Preset
+ */
+//SubGhzRadioPreset subghz_txrx_get_preset(SubGhzTxRx* instance);
+
+/**
+ * Get string frequency and modulation
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param frequency Pointer to a string frequency
+ * @param modulation Pointer to a string modulation
+ */
+/*void subghz_txrx_get_frequency_and_modulation(
+    SubGhzTxRx* instance,
+    FuriString* frequency,
+    FuriString* modulation);*/
+
+/**
+ * Start TX CC1101
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param flipper_format Pointer to a FlipperFormat
+ * @return SubGhzTxRxStartTxState 
+ */
+SubGhzTxRxStartTxState subghz_txrx_tx_start(SubGhzTxRx* instance, FlipperFormat* flipper_format);
+
+/**
+ * Start RX CC1101
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+//void subghz_txrx_rx_start(SubGhzTxRx* instance);
+
+/**
+ * Stop TX/RX CC1101
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+void subghz_txrx_stop(SubGhzTxRx* instance);
+
+/**
+ * Set sleep mode CC1101
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+//void subghz_txrx_sleep(SubGhzTxRx* instance);
+
+/**
+ * Update frequency CC1101 in automatic mode (hopper)
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+//void subghz_txrx_hopper_update(SubGhzTxRx* instance);
+
+/**
+ * Get state hopper
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return SubGhzHopperState 
+ */
+//SubGhzHopperState subghz_txrx_hopper_get_state(SubGhzTxRx* instance);
+
+/**
+ * Set state hopper
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param state State hopper
+ */
+void subghz_txrx_hopper_set_state(SubGhzTxRx* instance, SubGhzHopperState state);
+
+/**
+ * Unpause hopper
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+//void subghz_txrx_hopper_unpause(SubGhzTxRx* instance);
+
+/**
+ * Set pause hopper
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ */
+//void subghz_txrx_hopper_pause(SubGhzTxRx* instance);
+
+/**
+ * Speaker on
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ */
+void subghz_txrx_speaker_on(SubGhzTxRx* instance);
+
+/**
+ * Speaker off
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ */
+void subghz_txrx_speaker_off(SubGhzTxRx* instance);
+
+/**
+ * Speaker mute
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ */
+//void subghz_txrx_speaker_mute(SubGhzTxRx* instance);
+
+/**
+ * Speaker unmute
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ */
+void subghz_txrx_speaker_unmute(SubGhzTxRx* instance);
+
+/**
+ * Set state speaker
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ * @param state State speaker
+ */
+void subghz_txrx_speaker_set_state(SubGhzTxRx* instance, SubGhzSpeakerState state);
+
+/**
+ * Get state speaker
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ * @return SubGhzSpeakerState 
+ */
+//SubGhzSpeakerState subghz_txrx_speaker_get_state(SubGhzTxRx* instance);
+
+/**
+ * load decoder by name protocol
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param name_protocol Name protocol
+ * @return bool True if the decoder is loaded 
+ */
+bool subghz_txrx_load_decoder_by_name_protocol(SubGhzTxRx* instance, const char* name_protocol);
+
+/**
+ * Get decoder
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return SubGhzProtocolDecoderBase* Pointer to a SubGhzProtocolDecoderBase
+ */
+SubGhzProtocolDecoderBase* subghz_txrx_get_decoder(SubGhzTxRx* instance);
+
+/**
+ * Set callback for save data
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param callback Callback for save data
+ * @param context Context for callback
+ */
+/*void subghz_txrx_set_need_save_callback(
+    SubGhzTxRx* instance,
+    SubGhzTxRxNeedSaveCallback callback,
+    void* context);*/
+
+/**
+ * Get pointer to a load data key
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return FlipperFormat* 
+ */
+FlipperFormat* subghz_txrx_get_fff_data(SubGhzTxRx* instance);
+
+/**
+ * Get pointer to a SugGhzSetting
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @return SubGhzSetting* 
+ */
+SubGhzSetting* subghz_txrx_get_setting(SubGhzTxRx* instance);
+
+/**
+ * Is it possible to save this protocol
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ * @return bool True if it is possible to save this protocol
+ */
+//bool subghz_txrx_protocol_is_serializable(SubGhzTxRx* instance);
+
+/**
+ * Is it possible to send this protocol
+ * 
+ * @param instance Pointer to a SubGhzTxRx 
+ * @return bool True if it is possible to send this protocol
+ */
+//bool subghz_txrx_protocol_is_transmittable(SubGhzTxRx* instance, bool check_type);
+
+/**
+ * Set filter, what types of decoder to use 
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param filter Filter
+ */
+//void subghz_txrx_receiver_set_filter(SubGhzTxRx* instance, SubGhzProtocolFlag filter);
+
+/**
+ * Set callback for receive data
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param callback Callback for receive data
+ * @param context Context for callback
+ */
+/*void subghz_txrx_set_rx_calback(
+    SubGhzTxRx* instance,
+    SubGhzReceiverCallback callback,
+    void* context);*/
+
+/**
+ * Set callback for Raw decoder, end of data transfer  
+ * 
+ * @param instance Pointer to a SubGhzTxRx
+ * @param callback Callback for Raw decoder, end of data transfer 
+ * @param context Context for callback
+ */
+void subghz_txrx_set_raw_file_encoder_worker_callback_end(
+    SubGhzTxRx* instance,
+    SubGhzProtocolEncoderRAWCallbackEnd callback,
+    void* context);
+
+/* Checking if an external radio device is connected
+* 
+* @param instance Pointer to a SubGhzTxRx
+* @param name Name of external radio device
+* @return bool True if is connected to the external radio device
+*/
+bool subghz_txrx_radio_device_is_external_connected(SubGhzTxRx* instance, const char* name);
+
+/* Set the selected radio device to use
+*
+* @param instance Pointer to a SubGhzTxRx
+* @param radio_device_type Radio device type
+* @return SubGhzRadioDeviceType Type of installed radio device
+*/
+SubGhzRadioDeviceType
+    subghz_txrx_radio_device_set(SubGhzTxRx* instance, SubGhzRadioDeviceType radio_device_type);
+
+/* Get the selected radio device to use
+*
+* @param instance Pointer to a SubGhzTxRx
+* @return SubGhzRadioDeviceType Type of installed radio device
+*/
+//SubGhzRadioDeviceType subghz_txrx_radio_device_get(SubGhzTxRx* instance);
+
+/* Get RSSI the selected radio device to use
+*
+* @param instance Pointer to a SubGhzTxRx
+* @return float RSSI
+*/
+//float subghz_txrx_radio_device_get_rssi(SubGhzTxRx* instance);
+
+/* Get name the selected radio device to use
+*
+* @param instance Pointer to a SubGhzTxRx
+* @return const char* Name of installed radio device
+*/
+const char* subghz_txrx_radio_device_get_name(SubGhzTxRx* instance);
+
+/* Get get intelligence whether frequency the selected radio device to use
+*
+* @param instance Pointer to a SubGhzTxRx
+* @return bool True if the frequency is valid
+*/
+bool subghz_txrx_radio_device_is_frequecy_valid(SubGhzTxRx* instance, uint32_t frequency);

+ 29 - 0
meal_pager/helpers/subghz/subghz_txrx_i.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include "subghz_txrx.h"
+
+struct SubGhzTxRx {
+    SubGhzWorker* worker;
+
+    SubGhzEnvironment* environment;
+    SubGhzReceiver* receiver;
+    SubGhzTransmitter* transmitter;
+    SubGhzProtocolDecoderBase* decoder_result;
+    FlipperFormat* fff_data;
+
+    SubGhzRadioPreset* preset;
+    SubGhzSetting* setting;
+
+    uint8_t hopper_timeout;
+    uint8_t hopper_idx_frequency;
+    bool is_database_loaded;
+    SubGhzHopperState hopper_state;
+
+    SubGhzTxRxState txrx_state;
+    SubGhzSpeakerState speaker_state;
+    const SubGhzDevice* radio_device;
+    SubGhzRadioDeviceType radio_device_type;
+
+    SubGhzTxRxNeedSaveCallback need_save_callback;
+    void* need_save_context;
+};

+ 77 - 0
meal_pager/helpers/subghz/subghz_types.h

@@ -0,0 +1,77 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+
+/** SubGhzNotification state */
+typedef enum {
+    SubGhzNotificationStateStarting,
+    SubGhzNotificationStateIDLE,
+    SubGhzNotificationStateTx,
+    SubGhzNotificationStateRx,
+    SubGhzNotificationStateRxDone,
+} SubGhzNotificationState;
+
+/** SubGhzTxRx state */
+typedef enum {
+    SubGhzTxRxStateIDLE,
+    SubGhzTxRxStateRx,
+    SubGhzTxRxStateTx,
+    SubGhzTxRxStateSleep,
+} SubGhzTxRxState;
+
+/** SubGhzHopperState state */
+typedef enum {
+    SubGhzHopperStateOFF,
+    SubGhzHopperStateRunnig,
+    SubGhzHopperStatePause,
+    SubGhzHopperStateRSSITimeOut,
+} SubGhzHopperState;
+
+/** SubGhzSpeakerState state */
+typedef enum {
+    SubGhzSpeakerStateDisable,
+    SubGhzSpeakerStateShutdown,
+    SubGhzSpeakerStateEnable,
+} SubGhzSpeakerState;
+
+/** SubGhzRadioDeviceType */
+typedef enum {
+    SubGhzRadioDeviceTypeAuto,
+    SubGhzRadioDeviceTypeInternal,
+    SubGhzRadioDeviceTypeExternalCC1101,
+} SubGhzRadioDeviceType;
+
+/** SubGhzRxKeyState state */
+typedef enum {
+    SubGhzRxKeyStateIDLE,
+    SubGhzRxKeyStateNoSave,
+    SubGhzRxKeyStateNeedSave,
+    SubGhzRxKeyStateBack,
+    SubGhzRxKeyStateStart,
+    SubGhzRxKeyStateAddKey,
+    SubGhzRxKeyStateExit,
+    SubGhzRxKeyStateRAWLoad,
+    SubGhzRxKeyStateRAWSave,
+} SubGhzRxKeyState;
+
+/** SubGhzLoadKeyState state */
+typedef enum {
+    SubGhzLoadKeyStateUnknown,
+    SubGhzLoadKeyStateOK,
+    SubGhzLoadKeyStateParseErr,
+    SubGhzLoadKeyStateProtocolDescriptionErr,
+} SubGhzLoadKeyState;
+
+/** SubGhzLock */
+typedef enum {
+    SubGhzLockOff,
+    SubGhzLockOn,
+} SubGhzLock;
+
+/** SubGhz load type file */
+typedef enum {
+    SubGhzLoadTypeFileNoLoad,
+    SubGhzLoadTypeFileKey,
+    SubGhzLoadTypeFileRaw,
+} SubGhzLoadTypeFile;

BIN=BIN
meal_pager/icons/KeyBackspaceSelected_16x9.png


BIN=BIN
meal_pager/icons/KeyBackspace_16x9.png


BIN=BIN
meal_pager/icons/KeySaveSelected_24x11.png


BIN=BIN
meal_pager/icons/KeySave_24x11.png


BIN=BIN
meal_pager/icons/meal_pager_10px.png


+ 165 - 0
meal_pager/meal_pager.c

@@ -0,0 +1,165 @@
+#include "meal_pager.h"
+
+bool meal_pager_custom_event_callback(void* context, uint32_t event) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    return scene_manager_handle_custom_event(app->scene_manager, event);
+}
+
+void meal_pager_tick_event_callback(void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    scene_manager_handle_tick_event(app->scene_manager);
+}
+
+//leave app if back button pressed
+bool meal_pager_navigation_event_callback(void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    return scene_manager_handle_back_event(app->scene_manager);
+}
+
+Meal_Pager* meal_pager_app_alloc() {
+    Meal_Pager* app = malloc(sizeof(Meal_Pager));
+    app->gui = furi_record_open(RECORD_GUI);
+    app->notification = furi_record_open(RECORD_NOTIFICATION);
+
+    //Turn backlight on, believe me this makes testing your app easier
+    notification_message(app->notification, &sequence_display_backlight_on);
+
+    //Scene additions
+    app->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_enable_queue(app->view_dispatcher);
+
+    app->scene_manager = scene_manager_alloc(&meal_pager_scene_handlers, app);
+    view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
+    view_dispatcher_set_navigation_event_callback(
+        app->view_dispatcher, meal_pager_navigation_event_callback);
+    view_dispatcher_set_tick_event_callback(
+        app->view_dispatcher, meal_pager_tick_event_callback, 100);
+    view_dispatcher_set_custom_event_callback(
+        app->view_dispatcher, meal_pager_custom_event_callback);
+    app->submenu = submenu_alloc();
+
+    // Set defaults, in case no config loaded
+    app->haptic = 1;
+    app->speaker = 1;
+    app->led = 1;
+    app->save_settings = 1;
+    app->pager_type = 0;
+    app->first_station = 0;
+    app->first_station_char = "0";
+    app->last_station = 10;
+    app->last_station_char = "10";
+    app->first_pager = 0;
+    app->first_pager_char = "0";
+    app->last_pager = 31;
+    app->last_pager_char = "31";
+    app->stop_transmit = false;
+    app->repeats = 1;
+    app->repeats_char = "1";
+    app->max_station = 8191;
+    app->max_pager = 999;
+
+    snprintf(app->text_store[0], 32, "%lu", app->first_station);
+
+    // Used for File Browser
+    app->dialogs = furi_record_open(RECORD_DIALOGS);
+    app->file_path = furi_string_alloc();
+
+    app->subghz = subghz_alloc();
+
+    // Load configs
+    meal_pager_read_settings(app);
+
+    view_dispatcher_add_view(
+        app->view_dispatcher, Meal_PagerViewIdMenu, submenu_get_view(app->submenu));
+    app->meal_pager_startscreen = meal_pager_startscreen_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        Meal_PagerViewIdStartscreen,
+        meal_pager_startscreen_get_view(app->meal_pager_startscreen));
+    app->meal_pager_transmit = meal_pager_transmit_alloc(app);
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        Meal_PagerViewIdTransmit,
+        meal_pager_transmit_get_view(app->meal_pager_transmit));
+
+    app->variable_item_list = variable_item_list_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher,
+        Meal_PagerViewIdSettings,
+        variable_item_list_get_view(app->variable_item_list));
+
+    app->int_input = int_input_alloc();
+    view_dispatcher_add_view(
+        app->view_dispatcher, Meal_PagerViewIdIntInput, int_input_get_view(app->int_input));
+
+    //End Scene Additions
+
+    snprintf(app->text_store[0], 20, "%lu", app->first_station);
+    snprintf(app->text_store[1], 20, "%lu", app->last_station);
+    snprintf(app->text_store[2], 20, "%lu", app->first_pager);
+    snprintf(app->text_store[3], 20, "%lu", app->last_pager);
+
+    return app;
+}
+
+void meal_pager_app_free(Meal_Pager* app) {
+    furi_assert(app);
+
+    // Scene manager
+    scene_manager_free(app->scene_manager);
+
+    // View Dispatcher
+    view_dispatcher_remove_view(app->view_dispatcher, Meal_PagerViewIdMenu);
+    view_dispatcher_remove_view(app->view_dispatcher, Meal_PagerViewIdTransmit);
+    view_dispatcher_remove_view(app->view_dispatcher, Meal_PagerViewIdSettings);
+    view_dispatcher_remove_view(app->view_dispatcher, Meal_PagerViewIdIntInput);
+    view_dispatcher_remove_view(app->view_dispatcher, Meal_PagerViewIdStartscreen);
+    submenu_free(app->submenu);
+    int_input_free(app->int_input);
+
+    view_dispatcher_free(app->view_dispatcher);
+    variable_item_list_free(app->variable_item_list);
+    meal_pager_transmit_free(app->meal_pager_transmit);
+    meal_pager_startscreen_free(app->meal_pager_startscreen);
+    furi_record_close(RECORD_GUI);
+
+    // Close File Browser
+    furi_record_close(RECORD_DIALOGS);
+    furi_record_close(RECORD_NOTIFICATION);
+    furi_string_free(app->file_path);
+
+    app->gui = NULL;
+    app->notification = NULL;
+
+    subghz_free(app->subghz);
+
+    //Remove whatever is left
+    free(app);
+}
+
+int32_t meal_pager_app(void* p) {
+    UNUSED(p);
+    FURI_LOG_D(TAG, "Started Meal Pager");
+
+    Meal_Pager* app = meal_pager_app_alloc();
+
+    view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
+
+    scene_manager_next_scene(
+        app->scene_manager, Meal_PagerSceneStartscreen); //Start with start screen
+    //scene_manager_next_scene(app->scene_manager, Meal_PagerSceneMenu); //if you want to directly start with Menu
+
+    furi_hal_power_suppress_charge_enter();
+
+    view_dispatcher_run(app->view_dispatcher);
+
+    meal_pager_save_settings(app);
+
+    furi_hal_power_suppress_charge_exit();
+    meal_pager_app_free(app);
+
+    return 0;
+}

+ 2 - 0
meal_pager/meal_pager.h

@@ -0,0 +1,2 @@
+#pragma once
+#include "meal_pager_i.h"

+ 104 - 0
meal_pager/meal_pager_i.h

@@ -0,0 +1,104 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <stdlib.h>
+#include <dialogs/dialogs.h>
+#include <notification/notification_messages.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/submenu.h>
+#include <gui/scene_manager.h>
+#include <gui/modules/variable_item_list.h>
+#include <gui/modules/button_menu.h>
+#include <gui/modules/dialog_ex.h>
+#include "scenes/meal_pager_scene.h"
+#include "views/meal_pager_startscreen.h"
+#include "views/meal_pager_transmit.h"
+#include "helpers/meal_pager_storage.h"
+#include "helpers/subghz/subghz_types.h"
+#include "helpers/subghz/subghz.h"
+#include "helpers/gui/int_input.h"
+
+#define TAG "Meal_Pager"
+
+#define SUBGHZ_APP_EXTENSION ".sub"
+#define SUBGHZ_APP_FOLDER ANY_PATH("subghz")
+#define MEAL_PAGER_VERSION "1.6"
+
+typedef struct Meal_PagerTransmit Meal_PagerTransmit;
+typedef struct SubGhz SubGhz;
+
+typedef struct {
+    Gui* gui;
+    NotificationApp* notification;
+    SubGhzNotificationState state_notifications;
+    ViewDispatcher* view_dispatcher;
+    Submenu* submenu;
+    SubGhz* subghz;
+    SceneManager* scene_manager;
+    VariableItemList* variable_item_list;
+    Meal_PagerStartscreen* meal_pager_startscreen;
+    Meal_PagerTransmit* meal_pager_transmit;
+    DialogsApp* dialogs; // File Browser
+    FuriString* file_path; // File Browser
+    uint32_t haptic;
+    uint32_t speaker;
+    uint32_t led;
+    uint32_t save_settings;
+    uint32_t pager_type;
+    uint32_t first_station;
+    char* first_station_char;
+    uint32_t last_station;
+    char* last_station_char;
+    uint32_t first_pager;
+    char* first_pager_char;
+    uint32_t last_pager;
+    char* last_pager_char;
+    uint32_t current_station;
+    uint32_t current_pager;
+    bool stop_transmit;
+    uint32_t repeats;
+    char* repeats_char;
+    IntInput* int_input;
+    char* text_buffer;
+    uint32_t max_station;
+    uint32_t max_pager;
+    char text_store[6][36];
+} Meal_Pager;
+
+typedef enum {
+    Meal_PagerViewIdStartscreen,
+    Meal_PagerViewIdMenu,
+    Meal_PagerViewIdTransmit,
+    Meal_PagerViewIdSettings,
+    Meal_PagerViewIdIntInput,
+} Meal_PagerViewId;
+
+typedef enum {
+    Meal_PagerPagerTypeT119,
+    Meal_PagerPagerTypeTD157,
+    Meal_PagerPagerTypeTD165,
+    Meal_PagerPagerTypeTD174,
+} Meal_PagerPagerType;
+
+typedef enum {
+    Meal_PagerHapticOff,
+    Meal_PagerHapticOn,
+} Meal_PagerHapticState;
+
+typedef enum {
+    Meal_PagerSpeakerOff,
+    Meal_PagerSpeakerOn,
+} Meal_PagerSpeakerState;
+
+typedef enum {
+    Meal_PagerLedOff,
+    Meal_PagerLedOn,
+} Meal_PagerLedState;
+
+typedef enum {
+    Meal_PagerSettingsOff,
+    Meal_PagerSettingsOn,
+} Meal_PagerSettingsStoreState;

+ 30 - 0
meal_pager/scenes/meal_pager_scene.c

@@ -0,0 +1,30 @@
+#include "meal_pager_scene.h"
+
+// Generate scene on_enter handlers array
+#define ADD_SCENE(prefix, name, id) prefix##_scene_##name##_on_enter,
+void (*const meal_pager_on_enter_handlers[])(void*) = {
+#include "meal_pager_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 meal_pager_on_event_handlers[])(void* context, SceneManagerEvent event) = {
+#include "meal_pager_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 meal_pager_on_exit_handlers[])(void* context) = {
+#include "meal_pager_scene_config.h"
+};
+#undef ADD_SCENE
+
+// Initialize scene handlers configuration structure
+const SceneManagerHandlers meal_pager_scene_handlers = {
+    .on_enter_handlers = meal_pager_on_enter_handlers,
+    .on_event_handlers = meal_pager_on_event_handlers,
+    .on_exit_handlers = meal_pager_on_exit_handlers,
+    .scene_num = Meal_PagerSceneNum,
+};

+ 29 - 0
meal_pager/scenes/meal_pager_scene.h

@@ -0,0 +1,29 @@
+#pragma once
+
+#include <gui/scene_manager.h>
+
+// Generate scene id and total number
+#define ADD_SCENE(prefix, name, id) Meal_PagerScene##id,
+typedef enum {
+#include "meal_pager_scene_config.h"
+    Meal_PagerSceneNum,
+} Meal_PagerScene;
+#undef ADD_SCENE
+
+extern const SceneManagerHandlers meal_pager_scene_handlers;
+
+// Generate scene on_enter handlers declaration
+#define ADD_SCENE(prefix, name, id) void prefix##_scene_##name##_on_enter(void*);
+#include "meal_pager_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 "meal_pager_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 "meal_pager_scene_config.h"
+#undef ADD_SCENE

+ 8 - 0
meal_pager/scenes/meal_pager_scene_config.h

@@ -0,0 +1,8 @@
+ADD_SCENE(meal_pager, startscreen, Startscreen)
+ADD_SCENE(meal_pager, menu, Menu)
+ADD_SCENE(meal_pager, transmit, Transmit)
+ADD_SCENE(meal_pager, settings, Settings)
+ADD_SCENE(meal_pager, set_first_station, SetFirstStation)
+ADD_SCENE(meal_pager, set_last_station, SetLastStation)
+ADD_SCENE(meal_pager, set_first_pager, SetFirstPager)
+ADD_SCENE(meal_pager, set_last_pager, SetLastPager)

+ 131 - 0
meal_pager/scenes/meal_pager_scene_menu.c

@@ -0,0 +1,131 @@
+#include "../meal_pager_i.h"
+#include "../helpers/meal_pager_led.h"
+#include "../helpers/meal_pager_storage.h"
+#include "../views/meal_pager_transmit.h"
+
+enum SubmenuIndex {
+    SubmenuIndexTransmit = 10,
+    SubmenuIndexSetFirstStation,
+    SubmenuIndexSetLastStation,
+    SubmenuIndexSetFirstPager,
+    SubmenuIndexSetLastPager,
+    SubmenuIndexScene3,
+    SubmenuIndexScene4,
+    SubmenuIndexScene5,
+    SubmenuIndexSettings,
+};
+
+void meal_pager_scene_menu_submenu_callback(void* context, uint32_t index) {
+    Meal_Pager* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void meal_pager_scene_menu_on_enter(void* context) {
+    Meal_Pager* app = context;
+
+    meal_pager_set_max_values(app);
+
+    submenu_add_item(
+        app->submenu,
+        "Send Data",
+        SubmenuIndexTransmit,
+        meal_pager_scene_menu_submenu_callback,
+        app);
+    submenu_add_item(
+        app->submenu,
+        "Set First Station",
+        SubmenuIndexSetFirstStation,
+        meal_pager_scene_menu_submenu_callback,
+        app);
+    submenu_add_item(
+        app->submenu,
+        "Set Last Station",
+        SubmenuIndexSetLastStation,
+        meal_pager_scene_menu_submenu_callback,
+        app);
+    submenu_add_item(
+        app->submenu,
+        "Set First Pager",
+        SubmenuIndexSetFirstPager,
+        meal_pager_scene_menu_submenu_callback,
+        app);
+    submenu_add_item(
+        app->submenu,
+        "Set Last Pager",
+        SubmenuIndexSetLastPager,
+        meal_pager_scene_menu_submenu_callback,
+        app);
+    submenu_add_item(
+        app->submenu,
+        "Settings",
+        SubmenuIndexSettings,
+        meal_pager_scene_menu_submenu_callback,
+        app);
+
+    submenu_set_selected_item(
+        app->submenu, scene_manager_get_scene_state(app->scene_manager, Meal_PagerSceneMenu));
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, Meal_PagerViewIdMenu);
+}
+
+bool meal_pager_scene_menu_on_event(void* context, SceneManagerEvent event) {
+    Meal_Pager* app = context;
+    UNUSED(app);
+    if(event.type == SceneManagerEventTypeBack) {
+        //exit app
+        scene_manager_stop(app->scene_manager);
+        view_dispatcher_stop(app->view_dispatcher);
+        return true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == SubmenuIndexTransmit) {
+            scene_manager_set_scene_state(
+                app->scene_manager, Meal_PagerSceneMenu, SubmenuIndexTransmit);
+            scene_manager_next_scene(app->scene_manager, Meal_PagerSceneTransmit);
+            return true;
+        } else if(event.event == SubmenuIndexSettings) {
+            scene_manager_set_scene_state(
+                app->scene_manager, Meal_PagerSceneMenu, SubmenuIndexSettings);
+            scene_manager_next_scene(app->scene_manager, Meal_PagerSceneSettings);
+            return true;
+        } else if(event.event == Meal_PagerCustomEventViewTransmitterSendStop) {
+            app->state_notifications = SubGhzNotificationStateIDLE;
+            subghz_txrx_stop(app->subghz->txrx);
+            FURI_LOG_D(TAG, "Stop Event from Menu");
+            return true;
+        } else if(event.event == SubmenuIndexSetFirstStation) {
+            scene_manager_set_scene_state(
+                app->scene_manager, Meal_PagerSceneSetFirstStation, SubmenuIndexSetFirstStation);
+            scene_manager_next_scene(app->scene_manager, Meal_PagerSceneSetFirstStation);
+            return true;
+        } else if(event.event == SubmenuIndexSetLastStation) {
+            scene_manager_set_scene_state(
+                app->scene_manager, Meal_PagerSceneSetLastStation, SubmenuIndexSetLastStation);
+            scene_manager_next_scene(app->scene_manager, Meal_PagerSceneSetLastStation);
+            return true;
+        } else if(event.event == SubmenuIndexSetFirstPager) {
+            scene_manager_set_scene_state(
+                app->scene_manager, Meal_PagerSceneSetFirstPager, SubmenuIndexSetFirstPager);
+            scene_manager_next_scene(app->scene_manager, Meal_PagerSceneSetFirstPager);
+            return true;
+        } else if(event.event == SubmenuIndexSetLastPager) {
+            scene_manager_set_scene_state(
+                app->scene_manager, Meal_PagerSceneSetLastPager, SubmenuIndexSetLastPager);
+            scene_manager_next_scene(app->scene_manager, Meal_PagerSceneSetLastPager);
+            return true;
+        }
+    } else if(event.type == SceneManagerEventTypeTick) {
+        if(app->state_notifications == SubGhzNotificationStateTx) {
+            app->state_notifications = SubGhzNotificationStateIDLE;
+            subghz_txrx_stop(app->subghz->txrx);
+            meal_pager_blink_stop(app);
+            meal_pager_transmit_model_set_sending(app->meal_pager_transmit, 0);
+        }
+        return true;
+    }
+    return false;
+}
+
+void meal_pager_scene_menu_on_exit(void* context) {
+    Meal_Pager* app = context;
+    submenu_reset(app->submenu);
+}

+ 67 - 0
meal_pager/scenes/meal_pager_scene_set_first_pager.c

@@ -0,0 +1,67 @@
+#include "../meal_pager_i.h"
+#include "../helpers/meal_pager_custom_event.h"
+#include "../helpers/meal_pager_led.h"
+#include <dolphin/dolphin.h>
+
+void meal_pager_set_first_pager_callback(void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, Meal_PagerCustomerEventIntInput);
+}
+
+void meal_pager_scene_set_first_pager_on_enter(void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    IntInput* int_input = app->int_input;
+    size_t enter_name_length = 5;
+    meal_pager_set_max_values(app);
+    char* str = "Set First Pager (0 - 999)";
+    const char* constStr = str;
+    snprintf(str, 36, "Set First Pager (0 - %lu)", app->max_pager);
+
+    int_input_set_header_text(int_input, constStr);
+
+    int_input_set_result_callback(
+        int_input,
+        meal_pager_set_first_pager_callback,
+        context,
+        app->text_store[2],
+        enter_name_length,
+        false);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, Meal_PagerViewIdIntInput);
+}
+
+bool meal_pager_scene_set_first_pager_on_event(void* context, SceneManagerEvent event) {
+    Meal_Pager* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_previous_scene(app->scene_manager);
+        return true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        app->first_pager = atoi(app->text_store[2]);
+        if(app->first_pager > app->max_pager) {
+            app->first_pager = app->max_pager;
+            snprintf(app->text_store[2], 20, "%lu", app->first_pager);
+        }
+        app->first_pager_char = app->text_store[2];
+        scene_manager_previous_scene(app->scene_manager);
+        return true;
+    } else if(event.type == SceneManagerEventTypeTick) {
+        if(app->state_notifications == SubGhzNotificationStateTx) {
+            app->state_notifications = SubGhzNotificationStateIDLE;
+            subghz_txrx_stop(app->subghz->txrx);
+            meal_pager_blink_stop(app);
+            meal_pager_transmit_model_set_sending(app->meal_pager_transmit, 0);
+        }
+        return true;
+    }
+
+    return consumed;
+}
+
+void meal_pager_scene_set_first_pager_on_exit(void* context) {
+    Meal_Pager* app = context;
+    UNUSED(app);
+}

+ 67 - 0
meal_pager/scenes/meal_pager_scene_set_first_station.c

@@ -0,0 +1,67 @@
+#include "../meal_pager_i.h"
+#include "../helpers/meal_pager_custom_event.h"
+#include "../helpers/meal_pager_led.h"
+#include <dolphin/dolphin.h>
+
+void meal_pager_set_first_station_callback(void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, Meal_PagerCustomerEventIntInput);
+}
+
+void meal_pager_scene_set_first_station_on_enter(void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    IntInput* int_input = app->int_input;
+    size_t enter_name_length = 5;
+    meal_pager_set_max_values(app);
+    char* str = "Set First Station (0 - 9999)";
+    const char* constStr = str;
+    snprintf(str, 36, "Set First Station (0 - %lu)", app->max_station);
+
+    int_input_set_header_text(int_input, constStr);
+
+    int_input_set_result_callback(
+        int_input,
+        meal_pager_set_first_station_callback,
+        context,
+        app->text_store[0],
+        enter_name_length,
+        false);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, Meal_PagerViewIdIntInput);
+}
+
+bool meal_pager_scene_set_first_station_on_event(void* context, SceneManagerEvent event) {
+    Meal_Pager* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_previous_scene(app->scene_manager);
+        return true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        app->first_station = atoi(app->text_store[0]);
+        if(app->first_station > app->max_station) {
+            app->first_station = app->max_station;
+            snprintf(app->text_store[0], 20, "%lu", app->first_station);
+        }
+        app->first_station_char = app->text_store[0];
+        scene_manager_previous_scene(app->scene_manager);
+        return true;
+    } else if(event.type == SceneManagerEventTypeTick) {
+        if(app->state_notifications == SubGhzNotificationStateTx) {
+            app->state_notifications = SubGhzNotificationStateIDLE;
+            subghz_txrx_stop(app->subghz->txrx);
+            meal_pager_blink_stop(app);
+            meal_pager_transmit_model_set_sending(app->meal_pager_transmit, 0);
+        }
+        return true;
+    }
+
+    return consumed;
+}
+
+void meal_pager_scene_set_first_station_on_exit(void* context) {
+    Meal_Pager* app = context;
+    UNUSED(app);
+}

+ 71 - 0
meal_pager/scenes/meal_pager_scene_set_last_pager.c

@@ -0,0 +1,71 @@
+#include "../meal_pager_i.h"
+#include "../helpers/meal_pager_custom_event.h"
+#include "../helpers/meal_pager_led.h"
+#include <dolphin/dolphin.h>
+
+void meal_pager_set_last_pager_callback(void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, Meal_PagerCustomerEventIntInput);
+}
+
+void meal_pager_scene_set_last_pager_on_enter(void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    IntInput* int_input = app->int_input;
+    size_t enter_name_length = 5;
+    meal_pager_set_max_values(app);
+    char* str = "Set Last Pager (0 - 999)";
+    const char* constStr = str;
+    snprintf(str, 36, "Set Last Pager (%lu - %lu)", app->first_pager, app->max_pager);
+
+    int_input_set_header_text(int_input, constStr);
+
+    int_input_set_result_callback(
+        int_input,
+        meal_pager_set_last_pager_callback,
+        context,
+        app->text_store[3],
+        enter_name_length,
+        false);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, Meal_PagerViewIdIntInput);
+}
+
+bool meal_pager_scene_set_last_pager_on_event(void* context, SceneManagerEvent event) {
+    Meal_Pager* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_previous_scene(app->scene_manager);
+        return true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        app->last_pager = atoi(app->text_store[3]);
+        if(app->last_pager > app->max_pager) {
+            app->last_pager = app->max_pager;
+            snprintf(app->text_store[3], 20, "%lu", app->last_pager);
+        }
+        if(app->last_pager < app->first_pager) {
+            app->last_pager = app->first_pager;
+            snprintf(app->text_store[3], 20, "%lu", app->last_pager);
+        }
+        app->last_pager_char = app->text_store[3];
+        scene_manager_previous_scene(app->scene_manager);
+        return true;
+    } else if(event.type == SceneManagerEventTypeTick) {
+        if(app->state_notifications == SubGhzNotificationStateTx) {
+            app->state_notifications = SubGhzNotificationStateIDLE;
+            subghz_txrx_stop(app->subghz->txrx);
+            meal_pager_blink_stop(app);
+            meal_pager_transmit_model_set_sending(app->meal_pager_transmit, 0);
+        }
+        return true;
+    }
+
+    return consumed;
+}
+
+void meal_pager_scene_set_last_pager_on_exit(void* context) {
+    Meal_Pager* app = context;
+    UNUSED(app);
+}

+ 71 - 0
meal_pager/scenes/meal_pager_scene_set_last_station.c

@@ -0,0 +1,71 @@
+#include "../meal_pager_i.h"
+#include "../helpers/meal_pager_custom_event.h"
+#include "../helpers/meal_pager_led.h"
+#include <dolphin/dolphin.h>
+
+void meal_pager_set_last_station_callback(void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, Meal_PagerCustomerEventIntInput);
+}
+
+void meal_pager_scene_set_last_station_on_enter(void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    IntInput* int_input = app->int_input;
+    size_t enter_name_length = 5;
+    meal_pager_set_max_values(app);
+    char* str = "Set Last Station (0 - 9999)";
+    const char* constStr = str;
+    snprintf(str, 36, "Set Last Station (%lu - %lu)", app->first_station, app->max_station);
+
+    int_input_set_header_text(int_input, constStr);
+
+    int_input_set_result_callback(
+        int_input,
+        meal_pager_set_last_station_callback,
+        context,
+        app->text_store[1],
+        enter_name_length,
+        false);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, Meal_PagerViewIdIntInput);
+}
+
+bool meal_pager_scene_set_last_station_on_event(void* context, SceneManagerEvent event) {
+    Meal_Pager* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_previous_scene(app->scene_manager);
+        return true;
+    } else if(event.type == SceneManagerEventTypeCustom) {
+        app->last_station = atoi(app->text_store[1]);
+        if(app->last_station > app->max_station) {
+            app->last_station = app->max_station;
+            snprintf(app->text_store[1], 20, "%lu", app->last_station);
+        }
+        if(app->last_station < app->first_station) {
+            app->last_station = app->first_station;
+            snprintf(app->text_store[1], 20, "%lu", app->last_station);
+        }
+        app->last_station_char = app->text_store[1];
+        scene_manager_previous_scene(app->scene_manager);
+        return true;
+    } else if(event.type == SceneManagerEventTypeTick) {
+        if(app->state_notifications == SubGhzNotificationStateTx) {
+            app->state_notifications = SubGhzNotificationStateIDLE;
+            subghz_txrx_stop(app->subghz->txrx);
+            meal_pager_blink_stop(app);
+            meal_pager_transmit_model_set_sending(app->meal_pager_transmit, 0);
+        }
+        return true;
+    }
+
+    return consumed;
+}
+
+void meal_pager_scene_set_last_station_on_exit(void* context) {
+    Meal_Pager* app = context;
+    UNUSED(app);
+}

+ 237 - 0
meal_pager/scenes/meal_pager_scene_settings.c

@@ -0,0 +1,237 @@
+#include "../meal_pager_i.h"
+#include <lib/toolbox/value_index.h>
+
+enum SettingsIndex {
+    SettingsIndexHaptic = 10,
+    SettingsIndexValue1,
+    SettingsIndexValue2,
+};
+
+const char* const haptic_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t haptic_value[2] = {
+    Meal_PagerHapticOff,
+    Meal_PagerHapticOn,
+};
+
+const char* const pager_type_text[4] = {
+    "T119",
+    "TD157",
+    "TD165",
+    "TD174",
+};
+
+const uint32_t pager_type_value[4] = {
+    Meal_PagerPagerTypeT119,
+    Meal_PagerPagerTypeTD157,
+    Meal_PagerPagerTypeTD165,
+    Meal_PagerPagerTypeTD174,
+};
+
+const char* const speaker_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t speaker_value[2] = {
+    Meal_PagerSpeakerOff,
+    Meal_PagerSpeakerOn,
+};
+
+const char* const led_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t led_value[2] = {
+    Meal_PagerLedOff,
+    Meal_PagerLedOn,
+};
+
+const char* const settings_text[2] = {
+    "OFF",
+    "ON",
+};
+const uint32_t settings_value[2] = {
+    Meal_PagerSettingsOff,
+    Meal_PagerSettingsOn,
+};
+
+static void meal_pager_scene_settings_set_pager_type(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+
+    variable_item_set_current_value_text(item, pager_type_text[index]);
+    app->pager_type = pager_type_value[index];
+}
+
+static void meal_pager_scene_settings_set_first_station(VariableItem* item) {
+    UNUSED(item);
+}
+
+static void meal_pager_scene_settings_set_last_station(VariableItem* item) {
+    UNUSED(item);
+}
+
+static void meal_pager_scene_settings_set_first_pager(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint32_t index = variable_item_get_current_value_index(item);
+
+    snprintf(app->first_pager_char, 20, "%lu", index);
+    variable_item_set_current_value_text(item, app->first_pager_char);
+    app->first_pager = index;
+}
+
+static void meal_pager_scene_settings_set_last_pager(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint32_t index = variable_item_get_current_value_index(item);
+
+    snprintf(app->last_pager_char, 20, "%lu", index);
+    variable_item_set_current_value_text(item, app->last_pager_char);
+    app->last_pager = index;
+}
+
+static void meal_pager_scene_settings_set_repeats(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint32_t index = variable_item_get_current_value_index(item);
+
+    snprintf(app->repeats_char, 20, "%lu", index);
+    variable_item_set_current_value_text(item, app->repeats_char);
+    app->repeats = index;
+}
+
+/*static void meal_pager_scene_settings_set_haptic(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+
+    variable_item_set_current_value_text(item, haptic_text[index]);
+    app->haptic = haptic_value[index];
+}
+
+static void meal_pager_scene_settings_set_speaker(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, speaker_text[index]);
+    app->speaker = speaker_value[index];
+}*/
+
+static void meal_pager_scene_settings_set_led(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, led_text[index]);
+    app->led = led_value[index];
+}
+
+static void meal_pager_scene_settings_set_save_settings(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, settings_text[index]);
+    app->save_settings = settings_value[index];
+}
+
+void meal_pager_scene_settings_submenu_callback(void* context, uint32_t index) {
+    Meal_Pager* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, index);
+}
+
+void meal_pager_scene_settings_on_enter(void* context) {
+    Meal_Pager* app = context;
+    VariableItem* item;
+    uint8_t value_index;
+
+    // Pager Type
+    item = variable_item_list_add(
+        app->variable_item_list, "Pager Type:", 4, meal_pager_scene_settings_set_pager_type, app);
+    value_index = value_index_uint32(app->pager_type, pager_type_value, 4);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, pager_type_text[value_index]);
+
+    // First Station
+    item = variable_item_list_add(
+        app->variable_item_list,
+        "First Station",
+        1,
+        meal_pager_scene_settings_set_first_station,
+        app);
+    snprintf(app->first_pager_char, 20, "%lu", app->first_station);
+    variable_item_set_current_value_text(item, app->first_station_char);
+
+    // Last Station
+    item = variable_item_list_add(
+        app->variable_item_list,
+        "Last Station",
+        1,
+        meal_pager_scene_settings_set_last_station,
+        app);
+    snprintf(app->last_station_char, 20, "%lu", app->last_station);
+    variable_item_set_current_value_text(item, app->last_station_char);
+
+    // First Pager
+    item = variable_item_list_add(
+        app->variable_item_list, "First Pager", 1, meal_pager_scene_settings_set_first_pager, app);
+    snprintf(app->first_pager_char, 20, "%lu", app->first_pager);
+    variable_item_set_current_value_text(item, app->first_pager_char);
+
+    // Last Pager
+    item = variable_item_list_add(
+        app->variable_item_list, "Last Pager", 1, meal_pager_scene_settings_set_last_pager, app);
+    snprintf(app->last_pager_char, 20, "%lu", app->last_pager);
+    variable_item_set_current_value_text(item, app->last_pager_char);
+
+    // Repeat Attacks
+    item = variable_item_list_add(
+        app->variable_item_list, "Signal Repeat", 11, meal_pager_scene_settings_set_repeats, app);
+    variable_item_set_current_value_index(item, app->repeats);
+    snprintf(app->repeats_char, 20, "%lu", app->repeats);
+    variable_item_set_current_value_text(item, app->repeats_char);
+
+    // Vibro on/off Disabled until used
+    /*item = variable_item_list_add(
+        app->variable_item_list, "Vibro/Haptic:", 2, meal_pager_scene_settings_set_haptic, app);
+    value_index = value_index_uint32(app->haptic, haptic_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, haptic_text[value_index]);*/
+
+    // Sound on/off Disabled until used
+    /*item = variable_item_list_add(
+        app->variable_item_list, "Sound:", 2, meal_pager_scene_settings_set_speaker, app);
+    value_index = value_index_uint32(app->speaker, speaker_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, speaker_text[value_index]);*/
+
+    // LED Effects on/off
+    item = variable_item_list_add(
+        app->variable_item_list, "LED FX:", 2, meal_pager_scene_settings_set_led, app);
+    value_index = value_index_uint32(app->led, led_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, led_text[value_index]);
+
+    // Save Settings to File
+    item = variable_item_list_add(
+        app->variable_item_list,
+        "Save Settings",
+        2,
+        meal_pager_scene_settings_set_save_settings,
+        app);
+    value_index = value_index_uint32(app->save_settings, settings_value, 2);
+    variable_item_set_current_value_index(item, value_index);
+    variable_item_set_current_value_text(item, settings_text[value_index]);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, Meal_PagerViewIdSettings);
+}
+
+bool meal_pager_scene_settings_on_event(void* context, SceneManagerEvent event) {
+    Meal_Pager* app = context;
+    UNUSED(app);
+    bool consumed = false;
+    if(event.type == SceneManagerEventTypeCustom) {
+    }
+    return consumed;
+}
+
+void meal_pager_scene_settings_on_exit(void* context) {
+    Meal_Pager* app = context;
+    variable_item_list_set_selected_item(app->variable_item_list, 0);
+    variable_item_list_reset(app->variable_item_list);
+    meal_pager_set_max_values(app);
+}

+ 55 - 0
meal_pager/scenes/meal_pager_scene_startscreen.c

@@ -0,0 +1,55 @@
+#include "../meal_pager_i.h"
+#include "../helpers/meal_pager_custom_event.h"
+#include "../views/meal_pager_startscreen.h"
+
+void meal_pager_scene_startscreen_callback(Meal_PagerCustomEvent event, void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void meal_pager_scene_startscreen_on_enter(void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    meal_pager_startscreen_set_callback(
+        app->meal_pager_startscreen, meal_pager_scene_startscreen_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, Meal_PagerViewIdStartscreen);
+}
+
+bool meal_pager_scene_startscreen_on_event(void* context, SceneManagerEvent event) {
+    Meal_Pager* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case Meal_PagerCustomEventStartscreenLeft:
+        case Meal_PagerCustomEventStartscreenRight:
+            break;
+        case Meal_PagerCustomEventStartscreenUp:
+        case Meal_PagerCustomEventStartscreenDown:
+            break;
+        case Meal_PagerCustomEventStartscreenOk:
+            scene_manager_next_scene(app->scene_manager, Meal_PagerSceneMenu);
+            consumed = true;
+            break;
+        case Meal_PagerCustomEventStartscreenBack:
+            notification_message(app->notification, &sequence_reset_red);
+            notification_message(app->notification, &sequence_reset_green);
+            notification_message(app->notification, &sequence_reset_blue);
+            if(!scene_manager_search_and_switch_to_previous_scene(
+                   app->scene_manager, Meal_PagerSceneStartscreen)) {
+                scene_manager_stop(app->scene_manager);
+                view_dispatcher_stop(app->view_dispatcher);
+            }
+            consumed = true;
+            break;
+        }
+    }
+
+    return consumed;
+}
+
+void meal_pager_scene_startscreen_on_exit(void* context) {
+    Meal_Pager* app = context;
+    UNUSED(app);
+}

+ 108 - 0
meal_pager/scenes/meal_pager_scene_transmit.c

@@ -0,0 +1,108 @@
+#include "../meal_pager_i.h"
+#include "../helpers/meal_pager_custom_event.h"
+#include "../helpers/retekess/meal_pager_retekess_t119.h"
+#include "../helpers/retekess/meal_pager_retekess_td157.h"
+#include "../helpers/retekess/meal_pager_retekess_td165.h"
+#include "../helpers/retekess/meal_pager_retekess_td174.h"
+#include "../views/meal_pager_transmit.h"
+#include "../helpers/meal_pager_led.h"
+#include "../helpers/subghz/subghz.h"
+#include "../views/meal_pager_transmit.h"
+#include <dolphin/dolphin.h>
+
+void meal_pager_transmit_callback(Meal_PagerCustomEvent event, void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, event);
+}
+
+void meal_pager_scene_transmit_on_enter(void* context) {
+    furi_assert(context);
+    Meal_Pager* app = context;
+    FURI_LOG_D(TAG, "Type is %lu", app->pager_type);
+
+    app->stop_transmit = false;
+    meal_pager_blink_start_compile(app);
+    meal_pager_transmit_model_set_type(app->meal_pager_transmit, app->pager_type);
+    meal_pager_transmit_model_set_station(app->meal_pager_transmit, app->current_station);
+    meal_pager_transmit_model_set_pager(app->meal_pager_transmit, app->current_pager);
+    meal_pager_transmit_set_callback(app->meal_pager_transmit, meal_pager_transmit_callback, app);
+    view_dispatcher_switch_to_view(app->view_dispatcher, Meal_PagerViewIdTransmit);
+    bool generated = false;
+    switch(app->pager_type) {
+    case Meal_PagerPagerTypeT119:
+        generated = meal_pager_retekess_t119_generate_all(app);
+        break;
+    case Meal_PagerPagerTypeTD157:
+        generated = meal_pager_retekess_td157_generate_all(app);
+        break;
+    case Meal_PagerPagerTypeTD165:
+        generated = meal_pager_retekess_td165_generate_all(app);
+        break;
+    case Meal_PagerPagerTypeTD174:
+        generated = meal_pager_retekess_td174_generate_all(app);
+        break;
+    default:
+        generated = false;
+        break;
+    }
+    if(!generated) {
+        FURI_LOG_D(TAG, "Could not generate temp file");
+        meal_pager_blink_stop(app);
+        return;
+    }
+    FURI_LOG_D(TAG, "Generated tmp.sub");
+    FURI_LOG_D(TAG, "Start Transmitting");
+    if(!app->stop_transmit) {
+        meal_pager_transmit_model_set_sending(app->meal_pager_transmit, 1);
+        subghz_send(app);
+    }
+    dolphin_deed(DolphinDeedSubGhzSend);
+}
+
+bool meal_pager_scene_transmit_on_event(void* context, SceneManagerEvent event) {
+    Meal_Pager* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        switch(event.event) {
+        case Meal_PagerCustomEventTransmitLeft:
+        case Meal_PagerCustomEventTransmitRight:
+            break;
+        case Meal_PagerCustomEventTransmitUp:
+        case Meal_PagerCustomEventTransmitDown:
+            break;
+        case Meal_PagerCustomEventTransmitBack:
+            app->stop_transmit = true;
+            app->state_notifications = SubGhzNotificationStateIDLE;
+            meal_pager_blink_stop(app);
+            if(!scene_manager_search_and_switch_to_previous_scene(
+                   app->scene_manager, Meal_PagerSceneMenu)) {
+                scene_manager_stop(app->scene_manager);
+                view_dispatcher_stop(app->view_dispatcher);
+            }
+            consumed = true;
+            break;
+        case Meal_PagerCustomEventViewTransmitterSendStop:
+            app->state_notifications = SubGhzNotificationStateIDLE;
+            subghz_txrx_stop(app->subghz->txrx);
+            meal_pager_blink_stop(app);
+            meal_pager_transmit_model_set_sending(app->meal_pager_transmit, 0);
+            scene_manager_next_scene(app->scene_manager, Meal_PagerSceneMenu);
+            FURI_LOG_D(TAG, "Stop Event");
+            break;
+        }
+    } else if(event.type == SceneManagerEventTypeTick) {
+        if(app->state_notifications == SubGhzNotificationStateTx && app->led == 1) {
+            notification_message(app->notification, &sequence_blink_magenta_10);
+        }
+        return true;
+    }
+
+    return consumed;
+}
+
+void meal_pager_scene_transmit_on_exit(void* context) {
+    Meal_Pager* app = context;
+    UNUSED(app);
+}

+ 128 - 0
meal_pager/views/meal_pager_startscreen.c

@@ -0,0 +1,128 @@
+#include "meal_pager_startscreen.h"
+
+#include "../meal_pager_i.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+
+struct Meal_PagerStartscreen {
+    View* view;
+    Meal_PagerStartscreenCallback callback;
+    void* context;
+};
+
+typedef struct {
+    int some_value;
+} Meal_PagerStartscreenModel;
+
+void meal_pager_startscreen_set_callback(
+    Meal_PagerStartscreen* instance,
+    Meal_PagerStartscreenCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void meal_pager_startscreen_draw(Canvas* canvas, Meal_PagerStartscreenModel* model) {
+    UNUSED(model);
+    char buffer[64];
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignTop, "Restaurant Pager");
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str_aligned(canvas, 64, 22, AlignCenter, AlignTop, "Trigger Tool");
+    snprintf(buffer, sizeof(buffer), "Version: %s", MEAL_PAGER_VERSION);
+    canvas_draw_str_aligned(canvas, 64, 42, AlignCenter, AlignTop, buffer);
+    elements_button_center(canvas, "Start");
+}
+
+static void meal_pager_startscreen_model_init(Meal_PagerStartscreenModel* const model) {
+    model->some_value = 1;
+}
+
+bool meal_pager_startscreen_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    Meal_PagerStartscreen* instance = context;
+    if(event->type == InputTypeRelease) {
+        switch(event->key) {
+        case InputKeyBack:
+            with_view_model(
+                instance->view,
+                Meal_PagerStartscreenModel * model,
+                {
+                    UNUSED(model);
+                    instance->callback(Meal_PagerCustomEventStartscreenBack, instance->context);
+                },
+                true);
+            break;
+        case InputKeyLeft:
+        case InputKeyRight:
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyOk:
+            with_view_model(
+                instance->view,
+                Meal_PagerStartscreenModel * model,
+                {
+                    UNUSED(model);
+                    instance->callback(Meal_PagerCustomEventStartscreenOk, instance->context);
+                },
+                true);
+            break;
+        case InputKeyMAX:
+            break;
+        }
+    }
+    return true;
+}
+
+void meal_pager_startscreen_exit(void* context) {
+    furi_assert(context);
+}
+
+void meal_pager_startscreen_enter(void* context) {
+    furi_assert(context);
+    Meal_PagerStartscreen* instance = (Meal_PagerStartscreen*)context;
+    with_view_model(
+        instance->view,
+        Meal_PagerStartscreenModel * model,
+        { meal_pager_startscreen_model_init(model); },
+        true);
+}
+
+Meal_PagerStartscreen* meal_pager_startscreen_alloc() {
+    Meal_PagerStartscreen* instance = malloc(sizeof(Meal_PagerStartscreen));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(Meal_PagerStartscreenModel));
+    view_set_context(instance->view, instance); // furi_assert crashes in events without this
+    view_set_draw_callback(instance->view, (ViewDrawCallback)meal_pager_startscreen_draw);
+    view_set_input_callback(instance->view, meal_pager_startscreen_input);
+    //view_set_enter_callback(instance->view, meal_pager_startscreen_enter);
+    //view_set_exit_callback(instance->view, meal_pager_startscreen_exit);
+
+    with_view_model(
+        instance->view,
+        Meal_PagerStartscreenModel * model,
+        { meal_pager_startscreen_model_init(model); },
+        true);
+
+    return instance;
+}
+
+void meal_pager_startscreen_free(Meal_PagerStartscreen* instance) {
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view, Meal_PagerStartscreenModel * model, { UNUSED(model); }, true);
+    view_free(instance->view);
+    free(instance);
+}
+
+View* meal_pager_startscreen_get_view(Meal_PagerStartscreen* instance) {
+    furi_assert(instance);
+    return instance->view;
+}

+ 19 - 0
meal_pager/views/meal_pager_startscreen.h

@@ -0,0 +1,19 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/meal_pager_custom_event.h"
+
+typedef struct Meal_PagerStartscreen Meal_PagerStartscreen;
+
+typedef void (*Meal_PagerStartscreenCallback)(Meal_PagerCustomEvent event, void* context);
+
+void meal_pager_startscreen_set_callback(
+    Meal_PagerStartscreen* meal_pager_startscreen,
+    Meal_PagerStartscreenCallback callback,
+    void* context);
+
+View* meal_pager_startscreen_get_view(Meal_PagerStartscreen* meal_pager_static);
+
+Meal_PagerStartscreen* meal_pager_startscreen_alloc();
+
+void meal_pager_startscreen_free(Meal_PagerStartscreen* meal_pager_static);

+ 182 - 0
meal_pager/views/meal_pager_transmit.c

@@ -0,0 +1,182 @@
+#include "../meal_pager_i.h"
+#include <furi.h>
+#include <furi_hal.h>
+#include <input/input.h>
+#include <gui/elements.h>
+#include <dolphin/dolphin.h>
+
+const char* const pager_type_text_long[4] = {
+    "Retekess T119",
+    "Retekess TD157",
+    "Retekess TD165",
+    "Retekess TD174",
+};
+
+struct Meal_PagerTransmit {
+    View* view;
+    Meal_PagerTransmitCallback callback;
+    void* context;
+};
+
+typedef struct {
+    uint32_t pager_type;
+    uint32_t station;
+    uint32_t pager;
+    uint32_t sending;
+} Meal_PagerTransmitModel;
+
+void meal_pager_transmit_set_callback(
+    Meal_PagerTransmit* instance,
+    Meal_PagerTransmitCallback callback,
+    void* context) {
+    furi_assert(instance);
+    furi_assert(callback);
+    instance->callback = callback;
+    instance->context = context;
+}
+
+void meal_pager_transmit_draw(Canvas* canvas, Meal_PagerTransmitModel* model) {
+    canvas_clear(canvas);
+    canvas_set_color(canvas, ColorBlack);
+    canvas_set_font(canvas, FontPrimary);
+    //char* test = "";
+    //snprintf(test, 20, "%lu", model->pager_type);
+    char stationText[20] = "";
+    char pagerText[20] = "";
+    snprintf(stationText, 20, "Station: %lu", model->station);
+    snprintf(pagerText, 20, "Pager: %lu", model->pager);
+    canvas_draw_str_aligned(
+        canvas, 0, 10, AlignLeft, AlignTop, pager_type_text_long[model->pager_type]);
+    canvas_set_font(canvas, FontSecondary);
+    if(model->sending == 0) {
+        canvas_draw_str_aligned(canvas, 0, 22, AlignLeft, AlignTop, "Generating Data");
+        canvas_draw_str_aligned(canvas, 0, 32, AlignLeft, AlignTop, stationText);
+        canvas_draw_str_aligned(canvas, 0, 42, AlignLeft, AlignTop, pagerText);
+    } else {
+        canvas_draw_str_aligned(canvas, 0, 22, AlignLeft, AlignTop, "Sending Data");
+    }
+}
+
+static void meal_pager_transmit_model_init(Meal_PagerTransmitModel* const model) {
+    FURI_LOG_D(TAG, "Scene 1 Model Init");
+    model->pager_type = 0;
+    model->station = 0;
+    model->pager = 0;
+    model->sending = 0;
+}
+
+void meal_pager_transmit_model_set_type(Meal_PagerTransmit* instance, uint32_t type) {
+    furi_assert(instance);
+    Meal_PagerTransmitModel* model = view_get_model(instance->view);
+    model->pager_type = type;
+    view_commit_model(instance->view, false);
+}
+
+void meal_pager_transmit_model_set_station(Meal_PagerTransmit* instance, uint32_t station) {
+    furi_assert(instance);
+    Meal_PagerTransmitModel* model = view_get_model(instance->view);
+    model->station = station;
+    view_commit_model(instance->view, false);
+    with_view_model(
+        instance->view, Meal_PagerTransmitModel * model, { UNUSED(model); }, true);
+}
+
+void meal_pager_transmit_model_set_sending(Meal_PagerTransmit* instance, uint32_t value) {
+    furi_assert(instance);
+    Meal_PagerTransmitModel* model = view_get_model(instance->view);
+    model->sending = value;
+    view_commit_model(instance->view, false);
+    with_view_model(
+        instance->view, Meal_PagerTransmitModel * model, { UNUSED(model); }, true);
+}
+
+void meal_pager_transmit_model_set_pager(Meal_PagerTransmit* instance, uint32_t pager) {
+    furi_assert(instance);
+    Meal_PagerTransmitModel* model = view_get_model(instance->view);
+    model->pager = pager;
+    view_commit_model(instance->view, false);
+    with_view_model(
+        instance->view, Meal_PagerTransmitModel * model, { UNUSED(model); }, true);
+}
+
+bool meal_pager_transmit_input(InputEvent* event, void* context) {
+    furi_assert(context);
+    Meal_PagerTransmit* instance = context;
+    if(event->type == InputTypeRelease) {
+        switch(event->key) {
+        case InputKeyBack:
+            with_view_model(
+                instance->view,
+                Meal_PagerTransmitModel * model,
+                {
+                    UNUSED(model);
+                    instance->callback(Meal_PagerCustomEventTransmitBack, instance->context);
+                },
+                true);
+            break;
+        case InputKeyLeft:
+        case InputKeyRight:
+        case InputKeyUp:
+        case InputKeyDown:
+        case InputKeyOk:
+            with_view_model(
+                instance->view, Meal_PagerTransmitModel * model, { UNUSED(model); }, true);
+            break;
+        case InputKeyMAX:
+            break;
+        }
+    }
+    return true;
+}
+
+void meal_pager_transmit_exit(void* context) {
+    furi_assert(context);
+    FURI_LOG_D(TAG, "Scene 1 Exit");
+}
+
+void meal_pager_transmit_enter(void* context) {
+    FURI_LOG_D(TAG, "Scene 1 Enter");
+    furi_assert(context);
+    Meal_PagerTransmit* instance = (Meal_PagerTransmit*)context;
+    with_view_model(
+        instance->view, Meal_PagerTransmitModel * model, { UNUSED(model); }, true);
+}
+
+Meal_PagerTransmit* meal_pager_transmit_alloc(void* context) {
+    FURI_LOG_D(TAG, "Scene 1 Alloc");
+    furi_assert(context);
+    Meal_PagerTransmit* instance = malloc(sizeof(Meal_PagerTransmit));
+    instance->view = view_alloc();
+    view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(Meal_PagerTransmitModel));
+    view_set_context(instance->view, instance); // furi_assert crashes in events without this
+    view_set_draw_callback(instance->view, (ViewDrawCallback)meal_pager_transmit_draw);
+    view_set_input_callback(instance->view, meal_pager_transmit_input);
+    view_set_enter_callback(instance->view, meal_pager_transmit_enter);
+    view_set_exit_callback(instance->view, meal_pager_transmit_exit);
+
+    with_view_model(
+        instance->view,
+        Meal_PagerTransmitModel * model,
+        {
+            meal_pager_transmit_model_init(model);
+            //meal_pager_transmit_model_set_type(instance, 0);
+        },
+        true);
+
+    return instance;
+}
+
+void meal_pager_transmit_free(Meal_PagerTransmit* instance) {
+    FURI_LOG_D(TAG, "Transmit Free");
+    furi_assert(instance);
+
+    with_view_model(
+        instance->view, Meal_PagerTransmitModel * model, { UNUSED(model); }, true);
+    view_free(instance->view);
+    free(instance);
+}
+
+View* meal_pager_transmit_get_view(Meal_PagerTransmit* instance) {
+    furi_assert(instance);
+    return instance->view;
+}

+ 24 - 0
meal_pager/views/meal_pager_transmit.h

@@ -0,0 +1,24 @@
+#pragma once
+
+#include <gui/view.h>
+#include "../helpers/meal_pager_custom_event.h"
+
+typedef struct Meal_PagerTransmit Meal_PagerTransmit;
+
+typedef void (*Meal_PagerTransmitCallback)(Meal_PagerCustomEvent event, void* context);
+
+void meal_pager_transmit_set_callback(
+    Meal_PagerTransmit* meal_pager_transmit,
+    Meal_PagerTransmitCallback callback,
+    void* context);
+
+void meal_pager_transmit_model_set_sending(Meal_PagerTransmit* instance, uint32_t value);
+void meal_pager_transmit_model_set_type(Meal_PagerTransmit* instance, uint32_t type);
+void meal_pager_transmit_model_set_station(Meal_PagerTransmit* instance, uint32_t station);
+void meal_pager_transmit_model_set_pager(Meal_PagerTransmit* instance, uint32_t pager);
+
+View* meal_pager_transmit_get_view(Meal_PagerTransmit* meal_pager_static);
+
+Meal_PagerTransmit* meal_pager_transmit_alloc();
+
+void meal_pager_transmit_free(Meal_PagerTransmit* meal_pager_static);