David Lee 2 anni fa
commit
c593c54395

BIN
.DS_Store


+ 8 - 0
1-2-0.sub

@@ -0,0 +1,8 @@
+Filetype: Flipper SubGhz Key File
+Version: 1
+Frequency: 433920000
+Preset: FuriHalSubGhzPresetOok650Async
+Protocol: Princeton
+Bit: 24
+Key: 00 00 00 00 00 80 02 00
+TE: 271

+ 8 - 0
1-3-0.sub

@@ -0,0 +1,8 @@
+Filetype: Flipper SubGhz Key File
+Version: 1
+Frequency: 433920000
+Preset: FuriHalSubGhzPresetOok650Async
+Protocol: Princeton
+Bit: 24
+Key: 00 00 00 00 00 80 06 00
+TE: 271

+ 8 - 0
1-4-0.sub

@@ -0,0 +1,8 @@
+Filetype: Flipper SubGhz Key File
+Version: 1
+Frequency: 433920000
+Preset: FuriHalSubGhzPresetOok650Async
+Protocol: Princeton
+Bit: 24
+Key: 00 00 00 00 00 80 01 00
+TE: 271

+ 6 - 0
1_1-1_1-0.sub

@@ -0,0 +1,6 @@
+Filetype: Flipper SubGhz RAW File
+Version: 1
+Frequency: 433920000
+Preset: FuriHalSubGhzPresetOok650Async
+Protocol: RAW
+RAW_Data: -6000 600 -200 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 600 -200 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -6000

+ 9 - 0
1_1-1_4-0.sub

@@ -0,0 +1,9 @@
+Filetype: Flipper SubGhz RAW File
+Version: 1
+Frequency: 433920000
+Preset: FuriHalSubGhzPresetOok650Async
+Protocol: RAW
+RAW_Data: -6000 600 -200 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 600 -200 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -6000
+RAW_Data: -6000 600 -200 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 600 -200 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -6000
+RAW_Data: -6000 600 -200 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 600 -200 600 -200 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -6000
+RAW_Data: -6000 600 -200 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 600 -200 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -6000

+ 9 - 0
8191_8191-1_4-0.sub

@@ -0,0 +1,9 @@
+Filetype: Flipper SubGhz RAW File
+Version: 1
+Frequency: 433920000
+Preset: FuriHalSubGhzPresetOok650Async
+Protocol: RAW
+RAW_Data: -6000 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -6000
+RAW_Data: -6000 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 200 -600 600 -200 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -6000
+RAW_Data: -6000 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -6000
+RAW_Data: -6000 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 600 -200 200 -600 200 -600 600 -200 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -600 200 -6000

+ 514 - 0
Pagger - Retekess T119.html

@@ -0,0 +1,514 @@
+<!DOCTYPE html>
+<!-- saved from url=(0064)https://meoker.github.io/pagger/retekess-t119/retekess-t119.html -->
+<html lang="en" data-theme="light">
+
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <link rel="stylesheet" href="./Pagger - Retekess T119_files/pico.min.css">
+  <title>Pagger - Retekess T119</title>
+
+  <style>
+    [data-theme="light"],
+    :root:not([data-theme="dark"]) {
+      --primary: #757575;
+      --primary-hover: #616161;
+      --primary-focus: rgba(117, 117, 117, 0.125);
+      --primary-inverse: #FFF;
+    }
+
+    :root {
+      --font-size: 15px;
+      --spacing: 0.25rem;
+      --form-element-spacing-vertical: 0.375rem;
+    }
+
+    h1 {
+      margin-bottom: 0;
+    }
+
+    h3 {
+      color: #757575;
+      --typography-spacing-vertical: 0rem;
+    }
+
+    article {
+      padding-left: 0.75rem;
+      padding-right: 0.75rem;
+      margin: 1.75rem 0;
+    }
+
+    article>header,
+    article>footer {
+      text-align: center;
+      margin-left: -0.75rem;
+      margin-right: -0.75rem;
+      padding: 0.375rem;
+    }
+
+    .container>header {
+      text-align: center;
+    }
+
+    .container>footer {
+      display: flex;
+    }
+
+    .container>footer .title {
+      flex: 1;
+    }
+
+    .container>footer .github,
+    .container>footer .home {
+      margin-left: 10px;
+    }
+
+    .orange-theme {
+      --primary: #fb8c00;
+      --primary-hover: #f57c00;
+      --primary-focus: rgba(251, 140, 0, 0.125);
+      --primary-inverse: #FFF;
+    }
+
+    .orange-theme h3 {
+      color: #fb8c00;
+    }
+
+    .orange-theme input:not([type=submit], [type=button], [type=reset], [role=switch], [readonly]):is(:active, :focus) {
+      --border-color: #fb8c00;
+    }
+
+    .orange-theme input:not([type=submit], [type=button], [type=reset], [type=range], [type=file], [readonly]):focus,
+    .orange-theme select:focus,
+    .orange-theme textarea:focus {
+      --box-shadow: 0 0 0 3px rgba(251, 140, 0, 0.125);
+    }
+
+    .green-theme {
+      --primary: #7cb342;
+      --primary-hover: #689f38;
+      --primary-focus: rgba(124, 179, 66, 0.125);
+      --primary-inverse: #FFF;
+    }
+
+    .green-theme h3 {
+      color: #7cb342;
+    }
+
+    .green-theme input:not([type=submit], [type=button], [type=reset], [role=switch], [readonly]):is(:active, :focus) {
+      --border-color: #7cb342;
+    }
+
+    .green-theme input:not([type=submit], [type=button], [type=reset], [type=range], [type=file], [readonly]):focus,
+    .green-theme select:focus,
+    .green-theme textarea:focus {
+      --box-shadow: 0 0 0 3px rgba(124, 179, 66, 0.125);
+    }
+
+    .blue-theme {
+      --primary: #039be5;
+      --primary-hover: #0288d1;
+      --primary-focus: rgba(3, 155, 229, 0.125);
+      --primary-inverse: #FFF;
+    }
+
+    .blue-theme h3 {
+      color: #039be5;
+    }
+
+    .blue-theme input:not([type=submit], [type=button], [type=reset], [role=switch], [readonly]):is(:active, :focus) {
+      --border-color: #039be5;
+    }
+
+    .blue-theme input:not([type=submit], [type=button], [type=reset], [type=range], [type=file], [readonly]):focus,
+    .blue-theme select:focus,
+    .blue-theme textarea:focus {
+      --box-shadow: 0 0 0 3px rgba(3, 155, 229, 0.125);
+    }
+
+    @media (max-width: 992px) {
+      .grid .grid {
+        display: flex;
+      }
+
+      .grid .grid div {
+        display: flex;
+        flex-direction: column;
+        flex: 1;
+      }
+    }
+  </style>
+
+  <script>
+    function decToBin(num, length) {
+      var res = (num >>> 0).toString(2);
+
+      while (res.length < length) {
+        res = '0' + res;
+      }
+
+      return res;
+    }
+
+    function binToDec(bits) {
+      return parseInt(bits, 2);
+    }
+
+    function hexToBin(hex) {
+      return (parseInt(hex, 16).toString(2)).toUpperCase().padStart(24, '0');
+    }
+
+    function binToHex(bits) {
+      return parseInt(bits, 2).toString(16).toUpperCase().padStart(6, '0');
+    }
+
+    function invert(bits) {
+      return bits.split('').map(function (b) {
+        return (1 - b).toString();
+      }).join('');
+    }
+
+    function reverse(bits) {
+      return bits.split('').reverse().join('');
+    }
+
+    function encManchester(bits, mode) {
+      var res = '';
+
+      for (var i = 0; i < bits.length; i++) {
+        var c = bits.charAt(i);
+        '0' == c ? res += mode ? '10' : '01' : '1' == c ? res += mode ? '01' : '10' : res += 'EE';
+      }
+
+      return res;
+    }
+
+    function decManchester(bits, mode) {
+      var res = '';
+
+      for (var i = 1; i < bits.length; i += 2) {
+        var c = bits.charAt(i);
+        var p = bits.charAt(i - 1);
+        '0' == p && '1' == c ? res += mode ? '1' : '0' : '1' == p && '0' == c ? res += mode ? '0' : '1' : res += 'E';
+      }
+
+      return res;
+    }
+
+    function genKeyData(hex) {
+      var formatted = hex.replace(/(.{2})/g, '$1 ').trim();
+      var res = 'Key: ' + formatted + '\n';
+
+      return res;
+    }
+
+    function genRawData(zero, one, repeats, bits) {
+      bits += '0';
+      var line = 'RAW_Data: -6000';
+      //alert(bits);
+      console.log('bitsLen = ' + bits.length);
+      for (var i = 0; i < bits.length; i++) {
+        var c = bits.charAt(i);
+        var p = bits.charAt(i - 1);
+        var t = c == '0' ? zero : one;
+
+        if (i % 2 == 0) {
+          line += ' ' + t;
+        } else {
+          line += ' -' + t;
+        }
+      }
+
+      line += ' -6000\n';
+
+      var res = '';
+
+      for (var r = 0; r < repeats; r++) {
+        res += line;
+      }
+
+      return res;
+    }
+
+    function genKeyFile(keyData) {
+      var header = 'Filetype: Flipper SubGhz Key File\nVersion: 1\nFrequency: 433920000\nPreset: FuriHalSubGhzPresetOok650Async\nProtocol: Princeton\nBit: 24\n';
+      var footer = 'TE: 271\n';
+
+      return header + keyData + footer;
+    }
+
+    function genRawFile(rawData) {
+      var header = 'Filetype: Flipper SubGhz RAW File\nVersion: 1\nFrequency: 433920000\nPreset: FuriHalSubGhzPresetOok650Async\nProtocol: RAW\n'
+
+      return header + rawData;
+    }
+
+    function calcNumToHex(station, pager, action) {
+      station = reverse(decToBin(station, 13));
+      pager = reverse(decToBin(pager, 10));
+      action = reverse(decToBin(action, 1));
+
+      var hex = binToHex(station + pager + action);
+
+      return hex;
+    }
+
+    function calcHexToNum() {
+      var hex = document.getElementById('hex-key-sp').value.toString();
+      var bin = hexToBin(hex);
+
+      var station = binToDec(reverse(bin.substring(0, 13)));
+      var pager = binToDec(reverse(bin.substring(13, 23)));
+      var action = binToDec(reverse(bin.substring(23, 24)));
+
+      document.getElementById('station-num-kp').value = station;
+      document.getElementById('pager-num-kp').value = pager;
+      document.getElementById('action-num-kp').value = action;
+
+      document.getElementById('station-num-from-rp').value = station;
+      document.getElementById('station-num-to-rp').value = station;
+      document.getElementById('pager-num-from-rp').value = 1;
+      document.getElementById('pager-num-to-rp').value = 100;
+      document.getElementById('action-num-rp').value = action;
+
+      document.getElementById('station-num-tp').value = station;
+    }
+
+    function downloadKeyFile() {
+      document.getElementById('key-download').setAttribute('aria-busy', true);
+
+      var station = Number(document.getElementById('station-num-kp').value);
+      var pager = Number(document.getElementById('pager-num-kp').value);
+      var action = Number(document.getElementById('action-num-kp').value);
+
+      var hex = calcNumToHex(station, pager, action).toString().padStart(16, '0');
+      var keyData = genKeyData(hex);
+      var file = genKeyFile(keyData);
+
+      var hiddenElement = document.createElement('a');
+      hiddenElement.href = 'data:attachment/text,' + encodeURI(file);
+      hiddenElement.target = '_blank';
+      hiddenElement.download = station + '-' + pager + '-' + action + '.sub';
+      hiddenElement.click();
+
+      document.getElementById('key-download').setAttribute('aria-busy', false);
+    }
+
+    function downloadRawFile() {
+      document.getElementById('raw-download').setAttribute('aria-busy', true);
+
+      var stationFrom = Number(document.getElementById('station-num-from-rp').value);
+      var stationTo = Number(document.getElementById('station-num-to-rp').value);
+      var pagerFrom = Number(document.getElementById('pager-num-from-rp').value);
+      var pagerTo = Number(document.getElementById('pager-num-to-rp').value);
+      var action = Number(document.getElementById('action-num-rp').value);
+
+      var rawData = '';
+      var actionBin = reverse(decToBin(action, 1));
+
+      for (var s = stationFrom; s <= stationTo; s++) {
+        var stationBin = reverse(decToBin(s, 13));
+        //alert(stationBin);
+
+        for (var p = pagerFrom; p <= pagerTo; p++) {
+          var pagerBin = reverse(decToBin(p, 10));
+
+          //console.log('bin stationBin: ' + stationBin);
+          //console.log('bin stationPager: ' + stationBin + pagerBin);
+          console.log('bin fullId: ' + stationBin + pagerBin + actionBin);
+
+          var bin = encManchester(stationBin + pagerBin + actionBin, 0);
+          console.log('manchester: ' + bin);
+          //alert(bin);
+          var raw26 = genRawData(200, 600, 10, bin);
+          var raw38 = genRawData(300, 800, 10, bin);
+
+          console.log(raw26);
+          rawData += raw26 + raw38;
+        }
+      }
+      return;
+
+      var file = genRawFile(rawData);
+
+      var hiddenElement = document.createElement('a');
+      hiddenElement.href = 'data:attachment/text,' + encodeURI(file);
+      hiddenElement.target = '_blank';
+      hiddenElement.download = stationFrom + '_' + stationTo + '-' + pagerFrom + '_' + pagerTo + '-' + action + '.sub';
+      hiddenElement.click();
+
+      document.getElementById('raw-download').setAttribute('aria-busy', false);
+    }
+
+    function downloadTurnOffFile() {
+      document.getElementById('turnoff-download').setAttribute('aria-busy', true);
+
+      var station = Number(document.getElementById('station-num-tp').value);
+      var pager = Number(1005);
+      var action = Number(0);
+
+      var hex = calcNumToHex(station, pager, action).toString().padStart(16, '0');
+      var keyData = genKeyData(hex);
+      var file = genKeyFile(keyData);
+
+      var hiddenElement = document.createElement('a');
+      hiddenElement.href = 'data:attachment/text,' + encodeURI(file);
+      hiddenElement.target = '_blank';
+      hiddenElement.download = station + '-turnoff.sub';
+      hiddenElement.click();
+
+      document.getElementById('turnoff-download').setAttribute('aria-busy', false);
+    }
+  </script>
+</head>
+
+<body>
+  <main class="container">
+
+    <header>
+      <h1 class="title">RETEKESS T119</h1>
+    </header>
+
+    <article id="signal-pane" class="orange-theme">
+      <header>
+        <h3>SIGNAL</h3>
+      </header>
+      <div class="grid">
+        <div></div>
+        <div>
+          <label for="hex-key-sp">Hex key:</label>
+          <input name="hex-key-sp" id="hex-key-sp" placeholder="00AABB" minlength="6" maxlength="6">
+        </div>
+        <div></div>
+      </div>
+      <div class="grid">
+        <div></div>
+        <button onclick="calcHexToNum()">Calculate ↓</button>
+        <div></div>
+      </div>
+      <footer>
+        Put here the hex key you got from the read.
+      </footer>
+    </article>
+
+    <article id="key-pane" class="green-theme">
+      <header>
+        <h3>KEY FILE</h3>
+      </header>
+      <div class="grid">
+        <div>
+          <label for="station-num-kp">Station:</label>
+          <input type="number" name="station-num-kp" id="station-num-kp" min="0" max="8191" placeholder="0 - 8191">
+        </div>
+        <div>
+          <label for="pager-num-kp">Pager:</label>
+          <input type="number" name="pager-num-kp" id="pager-num-kp" min="0" max="999" placeholder="0 - 999">
+        </div>
+        <div>
+          <label for="action-num-kp">Action:</label>
+          <input type="number" name="action-num-kp" id="action-num-kp" min="0" max="1" placeholder="0 ring - 1 mute">
+        </div>
+      </div>
+      <div class="grid">
+        <div></div>
+        <button id="key-download" onclick="downloadKeyFile()">Download</button>
+        <div></div>
+      </div>
+      <footer>
+        This single key file will call a single pager.
+      </footer>
+    </article>
+
+    <article id="raw-pane" class="green-theme">
+      <header>
+        <h3>RAW FILE</h3>
+      </header>
+      <div class="grid">
+        <div class="grid">
+          <div>
+            <label for="station-num-from-rp">Station (from):</label>
+            <input type="number" name="station-num-from-rp" id="station-num-from-rp" min="0" max="8191"
+              placeholder="0 - 8191">
+          </div>
+          <div>
+            <label for="station-num-to-rp">Station (to):</label>
+            <input type="number" name="station-num-to-rp" id="station-num-to-rp" min="0" max="8191"
+              placeholder="0 - 8191">
+          </div>
+        </div>
+        <div class="grid">
+          <div>
+            <label for="pager-num-from-rp">Pager (from):</label>
+            <input type="number" name="pager-num-from-rp" id="pager-num-from-rp" min="0" max="999"
+              placeholder="0 - 999">
+          </div>
+          <div>
+            <label for="pager-num-to-rp">Pager (to):</label>
+            <input type="number" name="pager-num-to-rp" id="pager-num-to-rp" min="0" max="999" placeholder="0 - 999">
+          </div>
+        </div>
+        <div class="grid">
+          <div>
+            <label for="action-num-rp">Action:</label>
+            <input type="number" name="action-num-rp" id="action-num-rp" min="0" max="1" placeholder="0 ring - 1 mute">
+          </div>
+        </div>
+      </div>
+      <div class="grid">
+        <div></div>
+        <button id="raw-download" onclick="downloadRawFile()" aria-busy="false">Download</button>
+        <div></div>
+      </div>
+      <footer>
+        This combo raw file will call multiple pagers.
+      </footer>
+    </article>
+
+    <article id="turnoff-pane" class="green-theme">
+      <header>
+        <h3>TURN-OFF FILE</h3>
+      </header>
+      <div class="grid">
+        <div></div>
+        <div>
+          <label for="station-num-tp">Station:</label>
+          <input type="number" name="station-num-tp" id="station-num-tp" min="0" max="8191" placeholder="0 - 8191">
+          <button id="turnoff-download" onclick="downloadTurnOffFile()">Download</button>
+        </div>
+        <div></div>
+      </div>
+      <footer>
+        This special action file will turn off all the pagers of a given station.
+      </footer>
+    </article>
+
+    <article id="bruteforce-pane" class="blue-theme">
+      <header>
+        <h3>BRUTEFORCE FILES</h3>
+      </header>
+      <div class="grid">
+        <div></div>
+        <button
+          onclick="window.location.href=&#39;https://github.com/meoker/pagger/tree/main/retekess-t119/bruteforce-t119.zip&#39;">Download</button>
+        <div></div>
+      </div>
+      <footer>
+        As explained <a href="https://meoker.github.io/pagger/#why-not-a-bruteforcer">here</a> I don't think the
+        bruteforce is a viable approach... <br>Anyway if you wanted to try it, these are the files to call the first 10
+        pagers for all stations.
+      </footer>
+    </article>
+
+    <footer>
+      <div class="title">Pagger - Flipper Zero Generator</div>
+      <div class="github"><a href="https://github.com/meoker/pagger">Github</a></div>
+      <div class="home"><a href="https://meoker.github.io/pagger">Home</a></div>
+    </footer>
+
+  </main>
+
+
+</body>
+
+</html>

+ 31 - 0
README.md

@@ -0,0 +1,31 @@
+# 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
+- Select range of stations
+- Select range of pagers
+
+## How to install on Flipper Zero
+- If you do not have one, download a firmware<br>
+- Plug your Flipper Zero in via USB. <br>
+- Copy the contents of this folder into the applications_user folder of your firmware. <br> 
+
+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

+ 17 - 0
application.fam

@@ -0,0 +1,17 @@
+App(
+    appid="meal_pager",
+    name="Restaurant Pager Trigger",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="meal_pager_app",
+    cdefines=["APP_MEAL_PAGER"],
+    requires=[
+        "gui",
+        "storage",
+    ],
+    stack_size=2 * 1024,
+    order=10,
+    fap_libs=["assets"],
+    fap_icon="icons/meal_pager_10px.png",
+    fap_icon_assets="icons",
+    fap_category="Sub-Ghz",
+)

+ 60 - 0
helpers/meal_pager_custom_event.h

@@ -0,0 +1,60 @@
+#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_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;
+}

+ 36 - 0
helpers/meal_pager_haptic.c

@@ -0,0 +1,36 @@
+#include "meal_pager_haptic.h"
+#include "../meal_pager.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);
+    }
+}

+ 8 - 0
helpers/meal_pager_haptic.h

@@ -0,0 +1,8 @@
+#include <notification/notification_messages.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);
+

+ 39 - 0
helpers/meal_pager_led.c

@@ -0,0 +1,39 @@
+#include "meal_pager_led.h"
+#include "../meal_pager.h"
+
+
+
+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    
+}

+ 6 - 0
helpers/meal_pager_led.h

@@ -0,0 +1,6 @@
+
+
+void meal_pager_led_set_rgb(void* context, int red, int green, int blue);
+
+void meal_pager_led_reset(void* context);
+

+ 27 - 0
helpers/meal_pager_speaker.c

@@ -0,0 +1,27 @@
+#include "meal_pager_speaker.h"
+#include "../meal_pager.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();
+    }
+}

+ 4 - 0
helpers/meal_pager_speaker.h

@@ -0,0 +1,4 @@
+#define NOTE_INPUT 587.33f
+
+void meal_pager_play_input_sound(void* context);
+void meal_pager_stop_all_sound(void* context);

+ 136 - 0
helpers/meal_pager_storage.c

@@ -0,0 +1,136 @@
+#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_file_close(file);
+    flipper_format_free(file);
+}
+
+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_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);
+    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_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);
+
+    meal_pager_close_config_file(fff_file);
+    meal_pager_close_storage();
+}

+ 25 - 0
helpers/meal_pager_storage.h

@@ -0,0 +1,25 @@
+#pragma once
+
+#include <stdlib.h>
+#include <string.h>
+#include <storage/storage.h>
+#include <flipper_format/flipper_format_i.h>
+#include "../meal_pager.h"
+
+#define MEAL_PAGER_SETTINGS_FILE_VERSION 1
+#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_HAPTIC "Haptic"
+#define MEAL_PAGER_SETTINGS_KEY_LED "Led"
+#define MEAL_PAGER_SETTINGS_KEY_SPEAKER "Speaker"
+#define MEAL_PAGER_SETTINGS_KEY_SAVE_SETTINGS "SaveSettings"
+
+void meal_pager_save_settings(void* context);
+void meal_pager_read_settings(void* context);

+ 190 - 0
helpers/retekess/meal_pager_retekess_t119.c

@@ -0,0 +1,190 @@
+
+#include "meal_pager_retekess_t119.h"
+
+void customConcat(char* dest, const char* src) {
+    //FURI_LOG_D(TAG, "adding %s to %s", src, dest);
+    // 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* genRawData(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));
+
+    //FURI_LOG_D(TAG, "bitLen = %u", bitsLen);
+    // Initialize the line with the first part
+    //snprintf(line, lineLen, "-6000");
+
+    //char* res = (char*)malloc((bitsLen * 4) * sizeof(char));
+    char* res = (char*)malloc(lineLen * sizeof(char));
+    res[0] = '\0'; // Null-terminate the result string
+
+    //customConcat(res, line);
+    customConcat(res, "-6000");
+
+    // Append bits and create the line
+    for (int i = 0; i < bitsLen; i++) {
+        char c = bits[i];
+        //char p = (i > 0) ? bits[i - 1] : '0';
+        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
+        //strncat(res, line, bitsLen * 4);
+        customConcat(res, line);
+    }
+
+    // Append the closing part to the line
+    //strncat(line, " -6000\n", lineLen);
+    customConcat(res, " 200 -6000");
+    //FURI_LOG_D(TAG, "res is: %s", res);
+
+    free(line); // Free memory allocated for the line
+
+    return res;
+}
+
+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--;
+    }
+}
+ 
+void meal_pager_retekess_t119_generate_pager(void* context, char* stationId, uint32_t pager) {
+    Meal_Pager* app = context;
+    char pagerId[11];
+    //char stationPagerId[25];
+    //char fullId[25];
+    char* fullId = (char*)malloc(25 * sizeof(char));
+    uint32_t action = 0; // 0 = ring, 1 = mute
+    char actionId[2];
+    //char action[2];
+    //action[0] = '0'; // 0 = ring, 1 = mute
+    //action[1] = '\0';
+    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 = genRawData(200, 600, manchester);
+    FURI_LOG_D(TAG, "Raw Data: %s", rawSignal);
+    free(manchester);
+    free(rawSignal);
+}
+
+void meal_pager_retekess_t119_generate_station(void* context, uint32_t station) {
+    Meal_Pager* app = context;
+    FURI_LOG_D(TAG, "Generating T119 Data for Station %lu", station);
+    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 (u_int32_t i = app->current_pager;i <= app->last_pager; i++) {
+        meal_pager_retekess_t119_generate_pager(app, stationId, i);
+        //furi_thread_flags_wait(0, FuriFlagWaitAny, 1);
+    }
+}
+
+void meal_pager_retekess_t119_generate_all(void* context) {
+    Meal_Pager* app = context;
+
+    app->current_pager = 1;
+    app->current_station = app->first_station;
+
+    for (u_int32_t i = app->current_station;i <= app->last_station; i++) {
+        meal_pager_retekess_t119_generate_station(app, i);
+        //furi_thread_flags_wait(0, FuriFlagWaitAny, 100);
+    }
+}
+

+ 18 - 0
helpers/retekess/meal_pager_retekess_t119.h

@@ -0,0 +1,18 @@
+
+#pragma once
+
+#include "../../meal_pager.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);
+
+void meal_pager_retekess_t119_generate_pager(void* context, char* stationId, uint32_t pager);
+
+void meal_pager_retekess_t119_generate_station(void* context, uint32_t station);
+
+void meal_pager_retekess_t119_generate_all(void* context);

BIN
icons/meal_pager_10px.png


+ 127 - 0
meal_pager.c

@@ -0,0 +1,127 @@
+#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 = 255;
+    app->last_station_char = "255";
+    app->first_pager = 0;
+    app->first_pager_char = "0";
+    app->last_pager = 31;
+    app->last_pager_char = "31";
+
+    // Used for File Browser
+    app->dialogs = furi_record_open(RECORD_DIALOGS);
+    app->file_path = furi_string_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));
+
+    //End Scene Additions
+
+    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);
+    submenu_free(app->submenu);
+
+    view_dispatcher_free(app->view_dispatcher);
+    furi_record_close(RECORD_GUI);
+    
+    app->gui = NULL;
+    app->notification = NULL;
+
+    // Close File Browser
+    furi_record_close(RECORD_DIALOGS);
+    furi_string_free(app->file_path);
+
+    //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;
+}
+
+
+

+ 89 - 0
meal_pager.h

@@ -0,0 +1,89 @@
+#pragma once
+
+#include <furi.h>
+#include <furi_hal.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <stdlib.h>
+#include <assets_icons.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"
+
+#define TAG "Meal_Pager"
+
+#define SUBGHZ_APP_EXTENSION ".sub"
+#define SUBGHZ_APP_FOLDER ANY_PATH("subghz")
+
+typedef struct {
+    Gui* gui;
+    NotificationApp* notification;
+    ViewDispatcher* view_dispatcher;
+    Submenu* submenu;
+    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;
+} Meal_Pager;
+
+typedef enum {
+    Meal_PagerViewIdStartscreen,
+    Meal_PagerViewIdMenu,
+    Meal_PagerViewIdTransmit,
+    Meal_PagerViewIdSettings,
+} 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
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
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

+ 4 - 0
scenes/meal_pager_scene_config.h

@@ -0,0 +1,4 @@
+ADD_SCENE(meal_pager, startscreen, Startscreen)
+ADD_SCENE(meal_pager, menu, Menu)
+ADD_SCENE(meal_pager, transmit, Transmit)
+ADD_SCENE(meal_pager, settings, Settings)

+ 55 - 0
scenes/meal_pager_scene_menu.c

@@ -0,0 +1,55 @@
+#include "../meal_pager.h"
+
+enum SubmenuIndex {
+    SubmenuIndexTransmit = 10,
+    SubmenuIndexScene2,
+    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;
+
+    submenu_add_item(app->submenu, "Scene 1 (empty)", SubmenuIndexTransmit, 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;
+        }
+    }
+    return false;
+}
+
+void meal_pager_scene_menu_on_exit(void* context) {
+    Meal_Pager* app = context;
+    submenu_reset(app->submenu);
+}

+ 261 - 0
scenes/meal_pager_scene_settings.c

@@ -0,0 +1,261 @@
+#include "../meal_pager.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) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint32_t index = variable_item_get_current_value_index(item);
+
+    snprintf(app->first_station_char, 20, "%lu", index);
+    variable_item_set_current_value_text(item, app->first_station_char);
+    app->first_station = index;
+}
+
+static void meal_pager_scene_settings_set_last_station(VariableItem* item) {
+    Meal_Pager* app = variable_item_get_context(item);
+    uint32_t index = variable_item_get_current_value_index(item);
+
+    snprintf(app->last_station_char, 20, "%lu", index);
+    variable_item_set_current_value_text(item, app->last_station_char);
+    app->last_station = index;
+}
+
+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_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",
+        255,
+        meal_pager_scene_settings_set_first_station,
+        app);
+    variable_item_set_current_value_index(item, app->first_station);
+    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",
+        255,
+        meal_pager_scene_settings_set_last_station,
+        app);
+    variable_item_set_current_value_index(item, app->last_station);
+    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",
+        99,
+        meal_pager_scene_settings_set_first_pager,
+        app);
+    variable_item_set_current_value_index(item, app->first_pager);
+    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",
+        99,
+        meal_pager_scene_settings_set_last_pager,
+        app);
+    variable_item_set_current_value_index(item, app->last_pager);
+    snprintf(app->last_pager_char, 20, "%lu", app->last_pager);
+    variable_item_set_current_value_text(item, app->last_pager_char);
+
+    // Repeat Attacks
+
+    // Vibro on/off
+    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
+    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);
+}

+ 54 - 0
scenes/meal_pager_scene_startscreen.c

@@ -0,0 +1,54 @@
+#include "../meal_pager.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);
+}

+ 57 - 0
scenes/meal_pager_scene_transmit.c

@@ -0,0 +1,57 @@
+#include "../meal_pager.h"
+#include "../helpers/meal_pager_custom_event.h"
+#include "../helpers/retekess/meal_pager_retekess_t119.h"
+#include "../views/meal_pager_transmit.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);
+
+    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);
+    meal_pager_retekess_t119_generate_all(app);
+}
+
+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:
+                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_PagerSceneMenu)) {
+                        scene_manager_stop(app->scene_manager);
+                        view_dispatcher_stop(app->view_dispatcher);
+                    }
+                consumed = true;
+                break;
+        }
+    }
+    
+    return consumed;
+}
+
+void meal_pager_scene_transmit_on_exit(void* context) {
+    Meal_Pager* app = context;
+    UNUSED(app);
+}

+ 136 - 0
views/meal_pager_startscreen.c

@@ -0,0 +1,136 @@
+#include "../meal_pager.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);
+    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"); 
+    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
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);

+ 195 - 0
views/meal_pager_transmit.c

@@ -0,0 +1,195 @@
+#include "../meal_pager.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;
+} 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) {
+    UNUSED(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);
+    canvas_draw_str_aligned(canvas, 0, 22, AlignLeft, AlignTop, stationText); 
+    canvas_draw_str_aligned(canvas, 0, 32, AlignLeft, AlignTop, pagerText); 
+}
+
+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;
+}
+
+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_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
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_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);