Jelajahi Sumber

replace fm radio controller with new version

MX 2 tahun lalu
induk
melakukan
2c711af144

+ 1 - 1
ReadMe.md

@@ -189,7 +189,7 @@ The Flipper and its community wouldn't be as rich as it is without your contribu
 | Atomic Dice Roller | ![GPIO Badge] | [by nmrr](https://github.com/nmrr/flipperzero-atomicdiceroller) |  | ![None Badge] |
 | NRF24 Channel Scanner | ![GPIO Badge] | [by htotoo](https://github.com/htotoo/NRF24ChannelScanner) |  | [![Author Badge]](https://lab.flipper.net/apps/nrf24channelscanner) |
 | Mx2125 - Step Counter | ![GPIO Badge] | [by grugnoymeme](https://github.com/grugnoymeme/flipperzero-stepcounter-fap) |  | ![None Badge] |
-| TEA5767 Radio control | ![GPIO Badge] | [by victormico](https://github.com/victormico/flipperzero-radio/tree/main) |  | ![None Badge] |
+| TEA5767 Radio control | ![GPIO Badge] | [original by victormico](https://github.com/victormico/flipperzero-radio/tree/main) | new version [by coolshrimp](https://coolshrimp/flipperzero-firmware-wPlugins/) | ![None Badge] |
 | IR Remote | ![IR Badge] | [by Hong5489](https://github.com/Hong5489/ir_remote) | improvements [by friebel](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/pull/535) - Hold Option, RAW support [by d4ve10](https://github.com/d4ve10/ir_remote/tree/infrared_hold_option) | ![None Badge] |
 | IR Intervalometer | ![IR Badge] | [by Nitepone](https://github.com/Nitepone/flipper-intervalometer) |  | [![UFW Badge]](https://lab.flipper.net/apps/sony_intervalometer) |
 | IR Xbox Controller | ![IR Badge] | [by gebeto](https://github.com/gebeto/flipper-xbox-controller) |  | [![Author Badge]](https://lab.flipper.net/apps/xbox_controller) |

+ 274 - 0
non_catalog_apps/fm_radio_controller/TEA5767/TEA5767.c

@@ -0,0 +1,274 @@
+/**
+ * @file TEA5767.c
+ * @author Coolshrimp - CoolshrimpModz.com
+ * @brief Library for controlling the TEA5767 FM radio chip.
+ * @version 0.1
+ * @date 2023-09-29
+ * 
+ * @copyright GPLv3
+ */
+#include <furi.h>
+#include <furi_hal.h>
+#include <furi_hal_gpio.h>
+#include <furi_hal_resources.h>
+#include <stdio.h> // Include necessary libraries
+
+#include "TEA5767.h"
+
+#define TIMEOUT_MS 100
+// Define a structure to store station information
+
+struct StationInfo {
+    float frequency; // Frequency in MHz
+    int signalLevel; // Signal level
+};
+
+// Helper function to acquire I2C
+static bool acquire_i2c() {
+    // Acquire I2C and check for device readiness
+    furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
+    return furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external, TEA5767_ADR, 5);
+}
+
+static void release_i2c() {
+    // Release I2C
+    furi_hal_i2c_release(&furi_hal_i2c_handle_external);
+}
+
+bool tea5767_is_device_ready() {
+    bool result = acquire_i2c();
+    release_i2c();
+    return result;
+}
+
+bool tea5767_read_registers(uint8_t* buffer) {
+    if (buffer == NULL) return false;
+    bool result = acquire_i2c();
+    if(result) {
+        result = furi_hal_i2c_rx(&furi_hal_i2c_handle_external, TEA5767_ADR, buffer, 5, TIMEOUT_MS);
+    }
+    release_i2c();
+    return result;
+}
+
+bool tea5767_write_registers(uint8_t* buffer) {
+    bool result = false;
+    if (buffer == NULL) return false;  // Added NULL check
+        furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
+        result = furi_hal_i2c_tx(&furi_hal_i2c_handle_external, TEA5767_ADR, buffer, 5, TIMEOUT_MS);
+        furi_hal_i2c_release(&furi_hal_i2c_handle_external);
+    return result;
+}
+
+bool tea5767_init(uint8_t* buffer) {
+    bool result = false;
+
+    buffer[0] = 0x00;
+    buffer[1] = 0x00;
+    buffer[2] = 0xB0;
+    buffer[3] = REG_4_XTAL | REG_4_SMUTE;
+    buffer[4] = 0x00;
+
+    result = tea5767_write_registers(buffer);
+    return result;
+}
+
+bool tea5767_set_mute(uint8_t* buffer, bool mute) {
+    bool result = false;
+
+    if(mute) {
+        buffer[REG_1] |= REG_1_MUTE;
+    } else {
+        buffer[REG_1] &= ~REG_1_MUTE;
+    }
+
+    result = tea5767_write_registers(buffer);
+    return result;
+}
+
+bool tea5767_set_stereo(uint8_t* buffer, bool stereo) {
+    bool result = false;
+
+    if(stereo) {
+        buffer[REG_3] &= ~REG_3_MS;
+    } else {
+        buffer[REG_3] |= REG_3_MS;
+    }
+
+    result = tea5767_write_registers(buffer);
+    return result;
+}
+
+bool tea5767_seek(uint8_t* buffer, bool seek_up) {
+    bool result = false;
+    if (buffer == NULL) {return false;} 
+    buffer[REG_1] |= REG_1_SM;    // Set the Search Mode (SM) bit to initiate seek
+    if(seek_up) {    
+        buffer[REG_3] |= REG_3_SUD;  // Set Search Up (SUD) bit
+    } else {
+        buffer[REG_3] &= ~REG_3_SUD;  // Set Search Down (SUD) bit 
+    }
+    buffer[REG_3] |= (REG_3_SSL | 0x60); // Set the Search Stop Level (SSL) to high for better tuning accuracy,  set bit 7 for RSSI 7
+    buffer[REG_3] &= ~REG_3_MS;  // Set stereo mode (clearing the Mono bit)   
+    buffer[REG_4] &= ~REG_4_BL;   // Limit FM band 87.5 - 108 MHz.        
+    buffer[REG_5] |= REG_5_PLLREF;
+    // Write the updated register values to the TEA5767
+    result = tea5767_write_registers(buffer);
+    return result;    
+}
+
+bool tea5767_get_frequency(uint8_t* buffer, int* value) {
+    bool result = false;
+    uint16_t frequency;
+    if (buffer == NULL || value == NULL) return false;  //NULL check
+    if (tea5767_read_registers(buffer)) {
+        frequency = ((buffer[REG_1] & REG_1_PLL) << 8) | buffer[1];
+        *value = (frequency * QUARTZ / 4 - FILTER) / 10000;
+        result = true;
+    }
+    return result;
+}
+
+bool tea5767_set_frequency(uint8_t* buffer, int value) {
+    bool result = false;
+    if (buffer == NULL) {return false;}
+    uint16_t frequency = 4 * (value * 10000 + FILTER) / QUARTZ;
+    buffer[REG_1] = ((buffer[0] & ~REG_1_PLL) | ((frequency >> 8) & REG_1_PLL));  // Set the upper 8 bits of the PLL word
+    buffer[REG_2] = frequency & REG_2_PLL;  // Set the lower 8 bits of the PLL word
+    buffer[REG_3] &= ~REG_3_MS;  // Set stereo mode (clearing the Mono bit)   
+    buffer[REG_5] |= REG_5_PLLREF;
+    result = tea5767_write_registers(buffer);
+    return result;
+}
+
+bool tea5767_get_radio_info(uint8_t* buffer, struct RADIO_INFO* info) {
+    bool result = false;
+    int frequency_khz; 
+
+    // Error handling: Check if buffer and info are not NULL
+    if (buffer && info && tea5767_read_registers(buffer)) {
+        
+        if (buffer[REG_3] & REG_3_MS) {
+            info->stereo = true;
+        } else {
+            info->stereo = false;
+        }
+
+        info->signalLevel = buffer[REG_4] >> 4;
+
+        // Determine signal quality based on signal level
+        if (info->signalLevel >= 0 && info->signalLevel <= 3) {
+            strncpy(info->signalQuality, "Poor", sizeof(info->signalQuality));
+        } else if (info->signalLevel >= 4 && info->signalLevel <= 7) {
+            strncpy(info->signalQuality, "Fair", sizeof(info->signalQuality));
+        } else if (info->signalLevel >= 8 && info->signalLevel <= 11) {
+            strncpy(info->signalQuality, "Good", sizeof(info->signalQuality));
+        } else if (info->signalLevel >= 12 && info->signalLevel <= 15) {
+            strncpy(info->signalQuality, "Excellent", sizeof(info->signalQuality));
+        } else {
+            strncpy(info->signalQuality, "Unknown", sizeof(info->signalQuality));
+        }
+
+        // Now get the frequency
+        if (tea5767_get_frequency(buffer, &frequency_khz)) {
+            info->frequency = frequency_khz / 100.0f;  // Convert kHz to MHz
+            result = true;  // Only return true if both read_registers and get_frequency succeeded
+        }
+
+         // Check if the radio is muted
+        if (buffer[REG_1] & REG_1_MUTE) {
+            info->muted = true;
+        } else {
+            info->muted = false;
+        }
+
+    }
+    return result;
+}
+
+void tea5767_seekUp() {    
+    //Get CUrrent Station
+    double fm_frequency = tea5767_GetFreq();
+    int targetFrequencyKHz = fm_frequency * 100;
+        
+    uint8_t buffer[5];
+    if(tea5767_init(buffer)) { 
+        tea5767_set_frequency(buffer, targetFrequencyKHz);
+        // Start seeking upwards
+        tea5767_seek(buffer, true);
+    }
+}
+
+void tea5767_seekDown() {
+        //Get CUrrent Station
+    double fm_frequency = tea5767_GetFreq();
+    int targetFrequencyKHz = fm_frequency * 100;     
+    
+    uint8_t buffer[5];
+    if(tea5767_init(buffer)) {   
+        tea5767_set_frequency(buffer, targetFrequencyKHz);
+        // Start seeking upwards
+        tea5767_seek(buffer, false);
+    }
+}
+
+void tea5767_ToggleMute() {
+    uint8_t buffer[5];
+    if(tea5767_read_registers(buffer)) {
+        if((buffer[REG_1] & REG_1_MUTE) == 0) {
+            tea5767_set_mute(buffer, true);
+        } else {
+            tea5767_set_mute(buffer, false);
+        }
+    }
+}
+
+void tea5767_MuteOn() {
+    uint8_t buffer[5];
+    if(tea5767_read_registers(buffer)) {  // Read the current state into the buffer
+        tea5767_set_mute(buffer, true);  // Set the mute bit
+    }
+}
+
+void tea5767_MuteOff() {
+    uint16_t frequency;
+    float value;  // Changed to a float variable, not a pointer
+    uint8_t buffer[5];
+    if (tea5767_read_registers(buffer)) {  // Read the current state into the buffer
+        tea5767_set_mute(buffer, false);  // Clear the mute bit
+        frequency = ((buffer[0] & REG_1_PLL) << 8) | buffer[1];
+        value = (float)(frequency * QUARTZ / 4 - FILTER) / 10000;  // Explicitly cast to float
+        tea5767_SetFreqMHz(value/ 100.0);  // Pass the float value, not the pointer
+    }
+}
+
+void tea5767_SetFreqKHz(int freq_khz) { 
+    uint8_t buffer[5];
+    if (tea5767_init(buffer)) {
+        tea5767_set_frequency(buffer, freq_khz);
+    }
+}
+
+void tea5767_SetFreqMHz(float freq_mhz) { 
+    uint8_t buffer[5];
+    if (tea5767_init(buffer)) {
+        int freq_khz = (int)(freq_mhz * 100.0);  // Convert MHz to kHz
+        tea5767_set_frequency(buffer, freq_khz);
+    }
+}
+
+float tea5767_GetFreq() {
+    uint8_t buffer[5];
+    int value;
+    if(tea5767_get_frequency(buffer, &value)) {
+        return value / 100.0;  // Convert to MHz
+    }
+    return -1;  // Error
+}
+
+void tea5767_sleep(uint8_t *buffer) {
+    if(tea5767_read_registers(buffer)) {        
+        buffer[REG_4] |= REG_4_STBY; // Set the Standby bit in register 4 to enter standby mode
+        tea5767_write_registers(buffer);
+    }
+}

+ 105 - 0
non_catalog_apps/fm_radio_controller/TEA5767/TEA5767.h

@@ -0,0 +1,105 @@
+/**
+ * @file TEA5767.h
+ * @author Coolshrimp - CoolshrimpModz.com
+ * @brief Header file for controlling the TEA5767 FM radio chip.
+ * @version 0.1
+ * @date 2023-09-29
+ * 
+ * 
+ * TEA5767 Datasheet: https://www.sparkfun.com/datasheets/Wireless/General/TEA5767.pdf
+ * 
+ * 
+ * @copyright GPLv3
+ * 
+ */
+
+#ifndef TEA5767_H
+#define TEA5767_H
+
+
+#include <stdbool.h>
+#include <stdint.h>
+
+// Definitions for the Wire communication
+#define TEA5767_ADR 0xC0 // I2C address of TEA5767
+// #define TEA5767_ADR 0x60 // I2C address of TEA5767
+
+// Radio chip specific definitions including the registers
+#define QUARTZ 32768 // Frequency of the quartz crystal in Hz
+#define FILTER 225000 // Frequency of the filter in Hz
+
+// Define the registers
+#define REG_1 0x00  // Register 1 address
+#define REG_1_MUTE 0x80  // Mute: 1 to mute the audio output, 0 to unmute
+#define REG_1_SM 0x40  // Search Mode: 1 to activate search mode, 0 for normal mode
+#define REG_1_PLL 0x3F  // PLL Setting: Sets the PLL value (6 bits)
+
+#define REG_2 0x01  // Register 2 address
+#define REG_2_PLL 0xFF  // PLL Setting: Sets the PLL value (8 bits)
+
+#define REG_3 0x02  // Register 3 address
+#define REG_3_SUD 0x80  // Search Up/Down: 1 to search up, 0 to search down
+#define REG_3_SSL 0x60  // Search Stop Level: Sets the level at which the search stops (2 bits)
+#define REG_3_HLSI 0x10  // High/Low Side Injection: 1 for high side LO injection, 0 for low side LO injection
+#define REG_3_MS 0x08  // Mono to Stereo: 1 to force mono, 0 for stereo
+#define REG_3_MR 0x04  // Mute Right: 1 to mute the right audio channel and force mono, 0 to unmute
+#define REG_3_ML 0x02  // Mute Left: 1 to mute the left audio channel and force mono, 0 to unmute
+#define REG_3_SWP1 0x01  // Software programmable port 1: 1 for HIGH, 0 for LOW
+
+#define REG_4 0x03  // Register 4 address
+#define REG_4_SWP2 0x80  // Software programmable port 2: 1 for HIGH, 0 for LOW
+#define REG_4_STBY 0x40  // Standby: 1 to activate standby mode, 0 to deactivate
+#define REG_4_BL 0x20  // Band Limits: 1 for Japanese FM band, 0 for US/Europe FM band
+#define REG_4_XTAL 0x10  // Clock frequency: Sets the clock frequency (see Table 16)
+#define REG_4_SMUTE 0x08  // Soft Mute: 1 to activate soft mute, 0 to deactivate
+#define REG_4_HCC 0x04  // High Cut Control: 1 to activate high cut control, 0 to deactivate
+#define REG_4_SNC 0x02  // Stereo Noise Cancelling: 1 to activate stereo noise cancelling, 0 to deactivate
+#define REG_4_SI 0x01  // Search Indicator: 1 for ready flag output on pin SWPORT1, 0 for software programmable port 1
+
+#define REG_5 0x04  // Register 5 address
+#define REG_5_PLLREF 0x80  // PLL Ref: 1 to enable the 6.5 MHz reference frequency for the PLL, 0 to disable
+#define REG_5_DTC 0x40  // De-emphasis Time Constant: 1 for 75 µs, 0 for 50 µs
+
+// ----- local variables
+/// Band datatype.
+/// The BANDs TEA5767 can tune to.
+typedef enum {
+    RADIO_BAND_FM = 0x01,       ///< FM band 87.5 - 108 MHz (USA, Europe) selected.
+    RADIO_BAND_FM_JAPAN = 0x02  ///< FM band 76 - 90 MHz (Japan) selected.
+} RADIO_BAND;
+
+
+/// A structure that contains information about the radio features from the chip.
+struct RADIO_INFO {
+    float frequency; // Frequency in MHz
+    int signalLevel; // Signal level
+    bool stereo;     // Stereo or not
+    bool muted;      // Muted or not
+    char signalQuality[10];  // Field for signal quality text
+};
+
+
+// Function prototypes
+bool tea5767_is_device_ready();
+bool tea5767_read_registers(uint8_t* buffer);
+bool tea5767_write_registers(uint8_t* buffer);
+bool tea5767_init(uint8_t* buffer);
+bool tea5767_set_mute(uint8_t* buffer, bool mute);
+bool tea5767_set_stereo(uint8_t* buffer, bool stereo);
+bool tea5767_seek(uint8_t* buffer, bool seek_up);
+bool tea5767_get_frequency(uint8_t* buffer, int* value);
+bool tea5767_set_frequency(uint8_t* buffer, int value);
+bool tea5767_get_radio_info(uint8_t* buffer, struct RADIO_INFO* info);
+
+// New high-level function prototypes
+void tea5767_sleep(uint8_t* buffer);
+void tea5767_seekUp();
+void tea5767_seekDown();
+void tea5767_ToggleMute();
+void tea5767_MuteOn();
+void tea5767_MuteOff();
+void tea5767_SetFreqKHz(int freq_khz);
+void tea5767_SetFreqMHz(float freq_mhz);
+float tea5767_GetFreq();
+
+#endif // TEA5767_H

+ 55 - 0
non_catalog_apps/fm_radio_controller/TEA5767/examples.md

@@ -0,0 +1,55 @@
+/**
+ * Example calls for TEA5767.c library
+ * 
+ * Device Initialization and Readiness
+ * ----------------------------------
+ * uint8_t buffer[5];
+ * bool is_ready = tea5767_is_device_ready();
+ * 
+ * Read/Write Registers
+ * --------------------
+ * bool read_success = tea5767_read_registers(buffer);
+ * bool write_success = tea5767_write_registers(buffer);
+ * 
+ * Initialize the Device
+ * ---------------------
+ * bool init_success = tea5767_init(buffer);
+ * 
+ * Set Mute
+ * --------
+ * bool mute_success = tea5767_set_mute(buffer, true);  // To mute
+ * bool unmute_success = tea5767_set_mute(buffer, false);  // To unmute
+ * 
+ * Set Stereo
+ * ----------
+ * bool stereo_on = tea5767_set_stereo(buffer, true);  // Stereo ON
+ * bool stereo_off = tea5767_set_stereo(buffer, false);  // Stereo OFF
+ * 
+ * Seek
+ * ----
+ * bool seek_up_success = tea5767_seek(buffer, true);  // Seek up
+ * bool seek_down_success = tea5767_seek(buffer, false);  // Seek down
+ * 
+ * Get/Set Frequency
+ * -----------------
+ * int frequency_khz;
+ * bool get_freq_success = tea5767_get_frequency(buffer, &frequency_khz);
+ * bool set_freq_success = tea5767_set_frequency(buffer, 101900);  // Set to 101.9 MHz
+ * 
+ * Get Radio Information
+ * ---------------------
+ * struct RADIO_INFO radio_info;
+ * bool get_info_success = tea5767_get_radio_info(buffer, &radio_info);
+ * 
+ * Higher-Level Functions
+ * ----------------------
+ * tea5767_seekUp();  // Seek to next station up
+ * tea5767_seekDown();  // Seek to next station down
+ * tea5767_ToggleMute();  // Toggle mute state
+ * tea5767_MuteOn();  // Mute the device
+ * tea5767_MuteOff();  // Unmute the device
+ * tea5767_SetFreqKHz(101900);  // Set frequency to 101.9 MHz
+ * tea5767_SetFreqMHz(101.9);  // Set frequency to 101.9 MHz
+ * float current_freq = tea5767_GetFreq();  // Get current frequency in MHz
+ * tea5767_sleep(buffer);  // Put the device to sleep
+ */

+ 7 - 16
non_catalog_apps/fm_radio_controller/application.fam

@@ -1,23 +1,14 @@
 App(
     appid="fmradio_controller",
-    name="[TEA5767] Radio",
+    name="[TEA5767] FM Radio",
     apptype=FlipperAppType.EXTERNAL,
-    entry_point="radio_app",
-    requires=["gui"],
+    entry_point="fmradio_controller_app",
     stack_size=1 * 1024,
     fap_category="GPIO",
-    fap_private_libs=[
-        Lib(
-            name="TEA5767",
-            cincludes=["."],
-            sources=[
-                "TEA5767.c",
-            ],
-        ),
-    ],
     fap_icon="radio.png",
     fap_icon_assets="images",
-    fap_author="@victormico",
-    fap_version="0.1",
-    fap_description="Radio App",
-)
+    fap_author="coolshrimp",
+    fap_weburl="https://coolshrimp/flipperzero-firmware-wPlugins/",
+    fap_version="0.8",
+    fap_description="FM Radio App",
+)

TEMPAT SAMPAH
non_catalog_apps/fm_radio_controller/images/ButtonUp_7x4.png


+ 0 - 111
non_catalog_apps/fm_radio_controller/lib/TEA5767/TEA5767.c

@@ -1,111 +0,0 @@
-/**
- * @file TEA5767.c
- * @author Victor Micó 
- * @brief Implementation to control the TEA5767 radio chip.
- * @version 0.1
- * @date 2023-08-24
- * 
- * @copyright GPLv3
- * 
- * Ported from:
- * https://github.com/mathertel/Radio
- */
-#include <furi.h>
-#include <furi_hal.h>
-#include <stdint.h>
-#include "TEA5767.h"
-
-bool tea5767_is_device_ready() {
-    bool result = false;
-
-    furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
-    if(furi_hal_i2c_is_device_ready(&furi_hal_i2c_handle_external, TEA5767_ADR, 5)) {
-        result = true;
-    }
-    furi_hal_i2c_release(&furi_hal_i2c_handle_external);
-    return result;
-}
-
-bool tea5767_read_registers(uint8_t* buffer) {
-    bool result = false;
-
-    furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
-    if(furi_hal_i2c_rx(&furi_hal_i2c_handle_external, TEA5767_ADR, buffer, 5, 100)) {
-        result = true;
-    }
-    furi_hal_i2c_release(&furi_hal_i2c_handle_external);
-    return result;
-}
-
-bool tea5767_write_registers(uint8_t* buffer) {
-    bool result = false;
-
-    furi_hal_i2c_acquire(&furi_hal_i2c_handle_external);
-    if(furi_hal_i2c_tx(&furi_hal_i2c_handle_external, TEA5767_ADR, buffer, 5, 100)) {
-        result = true;
-    }
-    furi_hal_i2c_release(&furi_hal_i2c_handle_external);
-    return result;
-}
-
-bool tea5767_init(uint8_t* buffer) {
-    bool result = false;
-
-    buffer[0] = 0x00;
-    buffer[1] = 0x00;
-    buffer[2] = 0xB0;
-    buffer[3] = REG_4_XTAL | REG_4_SMUTE;
-    buffer[4] = 0x00;
-
-    result = tea5767_write_registers(buffer);
-    return result;
-}
-
-bool tea5767_set_mute(uint8_t* buffer, bool mute) {
-    bool result = false;
-
-    if(mute) {
-        buffer[REG_1] |= REG_1_MUTE;
-    } else {
-        buffer[REG_1] &= ~REG_1_MUTE;
-    }
-
-    result = tea5767_write_registers(buffer);
-    return result;
-}
-
-bool tea5767_get_frequency(uint8_t* buffer, int* value) {
-    bool result = false;
-    uint16_t frequency;
-
-    if(tea5767_read_registers(buffer)) {
-        frequency = ((buffer[0] & REG_1_PLL) << 8) | buffer[1];
-        *value = (frequency * QUARTZ / 4 - FILTER) / 10000;
-        result = true;
-    }
-    return result;
-}
-
-bool tea5767_set_frequency(uint8_t* buffer, int value) {
-    bool result = false;
-    uint16_t frequency = 4 * (value * 10000 + FILTER) / QUARTZ;
-
-    buffer[REG_1] = ((buffer[0] & ~REG_1_PLL) | ((frequency >> 8) & REG_1_PLL));
-    buffer[REG_2] = frequency & REG_2_PLL;
-
-    result = tea5767_write_registers(buffer);
-    return result;
-}
-
-bool tea5767_get_radio_info(uint8_t* buffer, struct RADIO_INFO* info) {
-    bool result = false;
-
-    if(tea5767_read_registers(buffer)) {
-        if(buffer[REG_3] & REG_3_STEREO) {
-            info->stereo = true;
-        }
-        info->rssi = (buffer[REG_4] & REG_4_ADC) >> 4;
-        result = true;
-    }
-    return result;
-}

+ 0 - 94
non_catalog_apps/fm_radio_controller/lib/TEA5767/TEA5767.h

@@ -1,94 +0,0 @@
-/**
- * @file TEA5767.h
- * @author Victor Micó 
- * @brief Implementation to control the TEA5767 radio chip.
- * @version 0.1
- * @date 2023-08-24
- * 
- * @copyright GPLv3
- * 
- * Ported from:
- * https://github.com/mathertel/Radio
- */
-
-#include <furi.h>
-#include <furi_hal.h>
-#include <stdint.h>
-
-// ----- Definitions for the Wire communication
-
-#define TEA5767_ADR 0xC0 // I2C address of TEA5767
-
-// ----- Radio chip specific definitions including the registers
-
-#define QUARTZ 32768
-#define FILTER 225000
-
-// Define the registers
-
-#define REG_1 0x00
-#define REG_1_MUTE 0x80
-#define REG_1_SM 0x40
-#define REG_1_PLL 0x3F
-
-#define REG_2 0x01
-#define REG_2_PLL 0xFF
-
-#define REG_3 0x02
-#define REG_3_MS 0x08
-#define REG_3_SSL 0x60
-#define REG_3_SUD 0x80
-#define REG_3_STEREO 0x80
-
-#define REG_4 0x03
-#define REG_4_SMUTE 0x08
-#define REG_4_XTAL 0x10
-#define REG_4_BL 0x20
-#define REG_4_STBY 0x40
-#define REG_4_ADC 0xF0
-
-#define REG_5 0x04
-#define REG_5_PLLREF 0x80
-#define REG_5_DTC 0x40
-
-// ----- local variables
-/// Band datatype.
-/// The BANDs a receiver probably can implement.
-enum RADIO_BAND {
-    RADIO_BAND_NONE = 0, ///< No band selected.
-
-    RADIO_BAND_FM = 0x01, ///< FM band 87.5 - 108 MHz (USA, Europe) selected.
-    RADIO_BAND_FMWORLD = 0x02, ///< FM band 76 - 108 MHz (Japan, Worldwide) selected.
-    RADIO_BAND_AM = 0x03, ///< AM band selected.
-    RADIO_BAND_KW = 0x04, ///< KW band selected.
-
-    RADIO_BAND_FMTX = 0x11, ///< Transmit for FM.
-};
-
-/// A structure that contains information about the radio features from the chip.
-struct RADIO_INFO {
-    bool active; ///< receiving is active.
-    uint8_t rssi; ///< Radio Station Strength Information.
-    uint8_t snr; ///< Signal Noise Ratio.
-    bool rds; ///< RDS information is available.
-    bool tuned; ///< A stable frequency is tuned.
-    bool mono; ///< Mono mode is on.
-    bool stereo; ///< Stereo audio is available
-};
-// store the current values of the 5 chip internal 8-bit registers
-// uint8_t registers[5]; ///< registers for controlling the radio chip.
-// uint8_t status[5]; ///< registers with the current status of the radio chip.
-
-/**
- * @brief Check if device is present
- * @param i2c address of the device  
- * @return True if present, false if not.
- */
-bool tea5767_is_device_ready();
-bool tea5767_read_registers(uint8_t* buffer);
-bool tea5767_write_registers(uint8_t* buffer);
-bool tea5767_init(uint8_t* buffer);
-bool tea5767_set_mute(uint8_t* buffer, bool mute);
-bool tea5767_get_frequency(uint8_t* buffer, int* value);
-bool tea5767_set_frequency(uint8_t* buffer, int value);
-bool tea5767_get_radio_info(uint8_t* buffer, struct RADIO_INFO* info);

+ 338 - 184
non_catalog_apps/fm_radio_controller/radio.c

@@ -1,220 +1,374 @@
+
+
+/**
+ *
+ * @author Coolshrimp - CoolshrimpModz.com
+ *
+ * @brief FM Radio using the TEA5767 FM radio chip.
+ * @version 0.8
+ * @date 2023-09-29
+ * 
+ * @copyright GPLv3
+ */
+
 #include <furi.h>
 #include <furi_hal.h>
+#include <stdint.h>
+#include <gui/gui.h>
+#include <gui/view.h>
+#include <gui/elements.h>
+#include <gui/view_dispatcher.h>
+#include <gui/modules/submenu.h>
+#include <gui/modules/widget.h>
+#include <gui/modules/variable_item_list.h>
+#include <notification/notification.h>
+#include <notification/notification_messages.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "TEA5767/TEA5767.h"
+
+// Define a macro for enabling the backlight always on.
+#define BACKLIGHT_ALWAYS_ON
+
+#define TAG "FMRadio"
+
+// Define volume options and names
+uint8_t volume_values[] = {0, 1};
+char* volume_names[] = {"Un-Muted", "Muted"};
+bool current_volume = 0;  // Muted or not
+char* current_vol = "Un-Muted";  // Current volume status as text. Changed type to char*
+int* signal_strength;
+int loopcount = 0;
+
+uint8_t tea5767_registers[5];
+
+// Define values for frequency selection
+float frequency_values[] = {
+    88.1, 88.9, 89.1, 90.3, 91.5, 91.7, 92.0, 92.5, 94.1, 95.9, 96.3, 96.9,
+    97.3, 98.1, 98.7, 99.1, 99.9, 100.7, 101.3, 103.9, 104.5, 105.1, 105.5, 106.5,
+    107.1, 102.7, 105.3
+};
+
+uint32_t current_frequency_index = 0;  // Default to the first frequency
+
+//lib can only do bottom left/right
+void elements_button_top_left(Canvas* canvas, const char* str) {
+    const uint8_t button_height = 12;
+    const uint8_t vertical_offset = 3;
+    const uint8_t horizontal_offset = 3;
+
+    // You may need to declare or pass 'button_width' here.
+    const uint8_t string_width = canvas_string_width(canvas, str);    
+
+    // 'button_width' should be declared or passed here.
+    const uint8_t button_width = string_width + horizontal_offset * 2 + 3;
+
+    const uint8_t x = 0;
+    const uint8_t y = 0 + button_height;
+
+    canvas_draw_box(canvas, x, y - button_height, button_width, button_height);
+    canvas_draw_line(canvas, x + button_width + 0, y - button_height, x + button_width + 0, y - 1);
+    canvas_draw_line(canvas, x + button_width + 1, y - button_height, x + button_width + 1, y - 2);
+    canvas_draw_line(canvas, x + button_width + 2, y - button_height, x + button_width + 2, y - 3);
+
+    canvas_invert_color(canvas);
+    canvas_draw_str(
+        canvas, x + horizontal_offset + 3, y - vertical_offset, str);
+    canvas_invert_color(canvas);
+}
 
-#include <furi_hal_gpio.h>
-#include <furi_hal_resources.h>
+void elements_button_top_right(Canvas* canvas, const char* str) {
+    const uint8_t button_height = 12;
+    const uint8_t vertical_offset = 3;
+    const uint8_t horizontal_offset = 3;
+    // You may need to declare or pass 'button_width' here.
+    const uint8_t string_width = canvas_string_width(canvas, str);
 
-#include <gui/gui.h>
-#include <locale/locale.h>
-#include <TEA5767.h>
+    // 'button_width' should be declared or passed here.
+    const uint8_t button_width = string_width + horizontal_offset * 2 + 3;
+
+    const uint8_t x = canvas_width(canvas);
+    const uint8_t y = 0 + button_height;
 
-/// The band that will be tuned by this sketch is FM.
-#define FIX_BAND RADIO_BAND_FM
+    canvas_draw_box(canvas, x - button_width, y - button_height, button_width, button_height);
+    canvas_draw_line(canvas, x - button_width - 1, y - button_height, x - button_width - 1, y - 1);
+    canvas_draw_line(canvas, x - button_width - 2, y - button_height, x - button_width - 2, y - 2);
+    canvas_draw_line(canvas, x - button_width - 3, y - button_height, x - button_width - 3, y - 3);
 
-/// The station that will be tuned by this sketch is 95.30 MHz.
-#define FIX_STATION 9530
+    canvas_invert_color(canvas);
+    canvas_draw_str(canvas, x - button_width + horizontal_offset, y - vertical_offset, str);
+    canvas_invert_color(canvas);
+}
 
+// Enumerations for submenu and view indices
 typedef enum {
-    RadioStateNotFound,
-    RadioStateFound,
-    RadioStateWriteSuccess,
-    RadioStateReadSuccess,
-    RadioStateWriteReadSuccess,
-} RadioState;
+    FMRadioSubmenuIndexConfigure,
+    FMRadioSubmenuIndexFlipTheWorld,
+    FMRadioSubmenuIndexAbout,
+} FMRadioSubmenuIndex;
 
 typedef enum {
-    DemoEventTypeTick,
-    DemoEventTypeKey,
-    // You can add additional events here.
-} DemoEventType;
+    FMRadioViewSubmenu,
+    FMRadioViewConfigure,
+    FMRadioViewFlipTheWorld,
+    FMRadioViewAbout,
+} FMRadioView;
 
+// Define a struct to hold the application's components
 typedef struct {
-    DemoEventType type; // The reason for this event.
-    InputEvent input; // This data is specific to keypress data.
-    // You can add additional data that is helpful for your events.
-} DemoEvent;
-
+    ViewDispatcher* view_dispatcher;
+    NotificationApp* notifications;
+    Submenu* submenu;
+    VariableItemList* variable_item_list_config;
+    View* view_flip_the_world;
+    Widget* widget_about;
+} FMRadio;
+
+// Define a model struct for your application
 typedef struct {
-    FuriString* buffer;
-    // You can add additional state here.
-    int address;
-    RadioState state;
-    int value;
-    uint8_t registers[5];
-} DemoData;
+    uint32_t frequency_index;
+    uint8_t volume_index;
+} MyModel;
 
-typedef struct {
-    FuriMessageQueue* queue; // Message queue (DemoEvent items to process).
-    FuriMutex* mutex; // Used to provide thread safe access to data.
-    DemoData* data; // Data accessed by multiple threads (acquire the mutex before accessing!)
-} DemoContext;
-
-// Invoked when input (button press) is detected.  We queue a message and then return to the caller.
-static void input_callback(InputEvent* input_event, FuriMessageQueue* queue) {
-    furi_assert(queue);
-    DemoEvent event = {.type = DemoEventTypeKey, .input = *input_event};
-    furi_message_queue_put(queue, &event, FuriWaitForever);
+// Callback for navigation events
+
+uint32_t fmradio_controller_navigation_exit_callback(void* context) {
+    UNUSED(context);
+    uint8_t buffer[5];  // Create a buffer to hold the TEA5767 register values
+        tea5767_sleep(buffer);  // Call the tea5767_sleep function, passing the buffer as an argument
+    return VIEW_NONE;
 }
 
-// Invoked by the timer on every tick.  We queue a message and then return to the caller.
-static void tick_callback(void* ctx) {
-    furi_assert(ctx);
-    FuriMessageQueue* queue = ctx;
-    DemoEvent event = {.type = DemoEventTypeTick};
-    // It's OK to loose this event if system overloaded (so we don't pass a wait value for 3rd parameter.)
-    furi_message_queue_put(queue, &event, 0);
+// Callback for navigating to the submenu
+uint32_t fmradio_controller_navigation_submenu_callback(void* context) {
+    UNUSED(context);
+    return FMRadioViewSubmenu;
 }
 
-// Invoked by the draw callback to render the screen. We render our UI on the callback thread.
-static void render_callback(Canvas* canvas, void* ctx) {
-    // Attempt to aquire context, so we can read the data.
-    DemoContext* demo_context = ctx;
-    if(furi_mutex_acquire(demo_context->mutex, 200) != FuriStatusOk) {
-        return;
+// Callback for handling submenu selections
+void fmradio_controller_submenu_callback(void* context, uint32_t index) {
+    FMRadio* app = (FMRadio*)context;
+    switch(index) {
+    case FMRadioSubmenuIndexConfigure:
+        view_dispatcher_switch_to_view(app->view_dispatcher, FMRadioViewConfigure);
+        break;
+    case FMRadioSubmenuIndexFlipTheWorld:
+        view_dispatcher_switch_to_view(app->view_dispatcher, FMRadioViewFlipTheWorld);
+        break;
+    case FMRadioSubmenuIndexAbout:
+        view_dispatcher_switch_to_view(app->view_dispatcher, FMRadioViewAbout);
+        break;
+    default:
+        break;
     }
+}
 
-    DemoData* data = demo_context->data;
-
-    canvas_set_font(canvas, FontPrimary);
-    if(data->address == TEA5767_ADR) {
-        canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignCenter, "FOUND I2C DEVICE");
-        furi_string_printf(data->buffer, "Address 0x%02x", (data->address));
-        canvas_draw_str_aligned(
-            canvas, 64, 30, AlignCenter, AlignCenter, furi_string_get_cstr(data->buffer));
-        if(data->state == RadioStateFound) {
-            canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, "FOUND DEVICE");
-        } else if(data->state == RadioStateWriteSuccess) {
-            canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, "WRITE SUCCESS");
-        } else if(data->state == RadioStateReadSuccess) {
-            canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, "READ SUCCESS");
-
-        } else if(data->state == RadioStateWriteReadSuccess) {
-            canvas_draw_str_aligned(
-                canvas, 64, 40, AlignCenter, AlignCenter, "WRITE/READ SUCCESS");
+bool fmradio_controller_view_input_callback(InputEvent* event, void* context) {
+    UNUSED(context);
+    if (event->type == InputTypeShort && event->key == InputKeyLeft) {
+        tea5767_seekDown();
+        current_volume = 0;
+        current_vol = "Un-Muted";
+        return true;  // Event was handled
+    } else if (event->type == InputTypeShort && event->key == InputKeyRight) {
+        tea5767_seekUp();
+        current_volume = 0;
+        current_vol = "Un-Muted";
+        return true;  // Event was handled
+    } else if (event->type == InputTypeShort && event->key == InputKeyOk) {       
+        if (current_volume == 0) {  // Fixed: == instead of =
+            tea5767_MuteOn();
+            current_volume = 1;
+            current_vol = "Muted";
+        } else {
+            tea5767_MuteOff();
+            current_volume = 0;
+            current_vol = "Un-Muted";
         }
-        // furi_string_printf(data->buffer, "value %d", (data->value));
-        furi_string_printf(
-            data->buffer,
-            "registers: %02X%02X%02X%02X%02X",
-            data->registers[0],
-            data->registers[1],
-            data->registers[2],
-            data->registers[3],
-            data->registers[4]);
-        canvas_draw_str_aligned(
-            canvas, 64, 50, AlignCenter, AlignCenter, furi_string_get_cstr(data->buffer));
-    } else {
-        canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignCenter, "I2C NOT FOUND");
-        canvas_draw_str_aligned(canvas, 64, 30, AlignCenter, AlignCenter, "pin15=SDA. pin16=SCL");
-        canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignCenter, "pin9=VCC. pin18=GND");
+        return true;  // Event was handled
+    } else if (event->type == InputTypeShort && event->key == InputKeyUp) {
+        // Increment the current frequency index and loop back if at the end
+        current_frequency_index = (current_frequency_index + 1) % (sizeof(frequency_values) / sizeof(frequency_values[0]));
+        // Set the new frequency
+        tea5767_SetFreqMHz(frequency_values[current_frequency_index]);
+        current_volume = 0;
+        current_vol = "Un-Muted";
+        return true;  // Event was handled
+    } else if (event->type == InputTypeShort && event->key == InputKeyDown) {
+        // Decrement the current frequency index and loop back if at the beginning
+        if (current_frequency_index == 0) {
+            current_frequency_index = (sizeof(frequency_values) / sizeof(frequency_values[0])) - 1;
+        } else {
+            current_frequency_index--;
+        }
+        // Set the new frequency
+        tea5767_SetFreqMHz(frequency_values[current_frequency_index]);
+        current_volume = 0;
+        current_vol = "Un-Muted";
+        return true;  // Event was handled
     }
-
-    // Release the context, so other threads can update the data.
-    furi_mutex_release(demo_context->mutex);
+    
+    return false;  // Event was not handled
 }
 
-// Our main loop invokes this method after acquiring the mutex, so we can safely access the protected data.
-static void update_i2c_status(void* ctx) {
-    DemoContext* demo_context = ctx;
-    DemoData* data = demo_context->data;
-
-    data->address = 0;
-    data->state = RadioStateNotFound;
-    if(tea5767_is_device_ready()) {
-        data->address = TEA5767_ADR;
-        data->state = RadioStateFound;
+// Callback for handling frequency changes
+void fmradio_controller_frequency_change(VariableItem* item) {
+    FMRadio* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    MyModel* model = view_get_model(app->view_flip_the_world);
+    model->frequency_index = index;
+
+    // Display the selected frequency value as text
+    char frequency_display[16];  // Adjust the buffer size as needed
+    snprintf(frequency_display, sizeof(frequency_display), "%.1f MHz", (double)frequency_values[index]);
+    variable_item_set_current_value_text(item, frequency_display);
+}
 
-        if(tea5767_init(data->registers)) {
-            data->state = RadioStateWriteSuccess;
+// Callback for handling volume changes
+void fmradio_controller_volume_change(VariableItem* item) {
+    FMRadio* app = variable_item_get_context(item);
+    uint8_t index = variable_item_get_current_value_index(item);
+    variable_item_set_current_value_text(item, volume_names[index]);  // Display the selected volume as text
+    MyModel* model = view_get_model(app->view_flip_the_world);
+    model->volume_index = index;
+}
 
-            int frequency = FIX_STATION;
+// Callback for drawing the view
+
+void fmradio_controller_view_draw_callback(Canvas* canvas, void* model) {
+    (void)model;  // Mark model as unused
+    
+    char frequency_display[64];    
+    char signal_display[64];
+    char volume_display[32]; 
+    
+    // tea5767_get_radio_info() populates the info
+    struct RADIO_INFO info;
+    uint8_t buffer[5];
+
+    // Draw strings on the canvas
+    canvas_draw_str(canvas, 45, 10, "FM Radio");    
+
+    // Draw button prompts
+    canvas_set_font(canvas, FontSecondary);
+    elements_button_left(canvas, "Scan-");
+    elements_button_right(canvas, "Scan+");
+    elements_button_center(canvas, "Mute");
+    elements_button_top_left(canvas, " Pre");
+    elements_button_top_right(canvas, "Pre ");
+    
+    
+    if (tea5767_get_radio_info(buffer, &info)) { 
+        snprintf(frequency_display, sizeof(frequency_display), "Frequency: %.1f MHz", (double)info.frequency);
+        canvas_draw_str(canvas, 10, 25, frequency_display);
+
+        snprintf(signal_display, sizeof(signal_display), "RSSI: %d (%s) %d", info.signalLevel, info.signalQuality, loopcount);
+        canvas_draw_str(canvas, 10, 45, signal_display); 
+
+        snprintf(volume_display, sizeof(volume_display), "Status: %s %s", info.muted ? "Playing" : "Muted", info.stereo ? "(Stereo)" : "(Mono)");
+        canvas_draw_str(canvas, 10, 35, volume_display);              
+    } else {
+        snprintf(frequency_display, sizeof(frequency_display), "TEA5767 Not Detected");
+        canvas_draw_str(canvas, 10, 25, frequency_display); 
 
-            if(tea5767_set_frequency(data->registers, frequency)) {
-                data->state = RadioStateWriteSuccess;
+        snprintf(signal_display, sizeof(signal_display), "Pin 15 = SDA | Pin 16 = SLC");
+        canvas_draw_str(canvas, 10, 45, signal_display); 
+    }   
 
-                data->value = 0;
-                if(tea5767_get_frequency(data->registers, &data->value)) {
-                    data->state = RadioStateReadSuccess;
-                }
-            }
-        }
-    }
 }
 
-int32_t radio_app(void* p) {
-    UNUSED(p);
+// Allocate memory for the application
+FMRadio* fmradio_controller_alloc() {
+    FMRadio* app = (FMRadio*)malloc(sizeof(FMRadio));
 
-    // Configure our initial data.
-    DemoContext* demo_context = malloc(sizeof(DemoContext));
-    demo_context->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
-    demo_context->data = malloc(sizeof(DemoData));
-    demo_context->data->buffer = furi_string_alloc();
-    demo_context->data->address = 0;
-    demo_context->data->state = RadioStateNotFound;
-    demo_context->data->value = 0;
-    demo_context->data->registers[0] = 0x00;
-    demo_context->data->registers[1] = 0x00;
-    demo_context->data->registers[2] = 0xB0;
-    demo_context->data->registers[3] = REG_4_XTAL | REG_4_SMUTE;
-    demo_context->data->registers[4] = 0x00;
-
-    // Queue for events (tick or input)
-    demo_context->queue = furi_message_queue_alloc(8, sizeof(DemoEvent));
-
-    // Set ViewPort callbacks
-    ViewPort* view_port = view_port_alloc();
-    view_port_draw_callback_set(view_port, render_callback, demo_context);
-    view_port_input_callback_set(view_port, input_callback, demo_context->queue);
-
-    // Open GUI and register view_port
     Gui* gui = furi_record_open(RECORD_GUI);
-    gui_add_view_port(gui, view_port, GuiLayerFullscreen);
-
-    // Update the screen fairly frequently (every 1000 milliseconds = 1 second.)
-    FuriTimer* timer = furi_timer_alloc(tick_callback, FuriTimerTypePeriodic, demo_context->queue);
-    furi_timer_start(timer, 1000);
-
-    // Main loop
-    DemoEvent event;
-    bool processing = true;
-    do {
-        if(furi_message_queue_get(demo_context->queue, &event, FuriWaitForever) == FuriStatusOk) {
-            switch(event.type) {
-            case DemoEventTypeKey:
-                // Short press of back button exits the program.
-                if(event.input.type == InputTypeShort && event.input.key == InputKeyBack) {
-                    processing = false;
-                }
-                break;
-            case DemoEventTypeTick:
-                // Every timer tick we update the i2c status.
-                furi_mutex_acquire(demo_context->mutex, FuriWaitForever);
-                update_i2c_status(demo_context);
-                furi_mutex_release(demo_context->mutex);
-                break;
-            default:
-                break;
-            }
-
-            // Send signal to update the screen (callback will get invoked at some point later.)
-            view_port_update(view_port);
-        } else {
-            // We had an issue getting message from the queue, so exit application.
-            processing = false;
-        }
-    } while(processing);
 
-    // Free resources
-    furi_timer_free(timer);
-    view_port_enabled_set(view_port, false);
-    gui_remove_view_port(gui, view_port);
-    view_port_free(view_port);
+    // Initialize the view dispatcher
+    app->view_dispatcher = view_dispatcher_alloc();
+    view_dispatcher_enable_queue(app->view_dispatcher);
+    view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen);
+
+    // Initialize the submenu
+    app->submenu = submenu_alloc();
+    submenu_add_item(app->submenu,"Listen Now",FMRadioSubmenuIndexFlipTheWorld,fmradio_controller_submenu_callback,app);
+    //submenu_add_item(app->submenu, "Config", FMRadioSubmenuIndexConfigure, fmradio_controller_submenu_callback, app);
+    submenu_add_item(app->submenu, "About", FMRadioSubmenuIndexAbout, fmradio_controller_submenu_callback, app);
+    view_set_previous_callback(submenu_get_view(app->submenu), fmradio_controller_navigation_exit_callback);
+    view_dispatcher_add_view(app->view_dispatcher, FMRadioViewSubmenu, submenu_get_view(app->submenu));
+    view_dispatcher_switch_to_view(app->view_dispatcher, FMRadioViewSubmenu);
+
+    // Initialize the variable item list for configuration
+    app->variable_item_list_config = variable_item_list_alloc();
+    variable_item_list_reset(app->variable_item_list_config);
+
+    // Add frequency configuration
+    VariableItem* frequency_item = variable_item_list_add(app->variable_item_list_config,"Freq (MHz)", COUNT_OF(frequency_values),fmradio_controller_frequency_change,app); 
+    uint32_t frequency_index = 0;
+    variable_item_set_current_value_index(frequency_item, frequency_index);
+
+    // Add volume configuration
+    VariableItem* volume_item = variable_item_list_add(app->variable_item_list_config,"Volume", COUNT_OF(volume_values),fmradio_controller_volume_change,app);
+    uint8_t volume_index = 0;
+    variable_item_set_current_value_index(volume_item, volume_index);
+    view_set_previous_callback(variable_item_list_get_view(app->variable_item_list_config),fmradio_controller_navigation_submenu_callback);
+    view_dispatcher_add_view(app->view_dispatcher,FMRadioViewConfigure,variable_item_list_get_view(app->variable_item_list_config));
+
+    // Initialize the view for flipping the world
+    app->view_flip_the_world = view_alloc();
+    view_set_draw_callback(app->view_flip_the_world, fmradio_controller_view_draw_callback);
+    view_set_input_callback(app->view_flip_the_world, fmradio_controller_view_input_callback);
+    view_set_previous_callback(app->view_flip_the_world, fmradio_controller_navigation_submenu_callback);
+    view_allocate_model(app->view_flip_the_world, ViewModelTypeLockFree, sizeof(MyModel));
+    MyModel* model = view_get_model(app->view_flip_the_world);
+    model->frequency_index = frequency_index;
+    model->volume_index = volume_index;
+
+    view_dispatcher_add_view(app->view_dispatcher, FMRadioViewFlipTheWorld, app->view_flip_the_world);
+
+    // Initialize the widget for displaying information about the app
+    app->widget_about = widget_alloc();
+    widget_add_text_scroll_element(app->widget_about,0,0,128,64,"FM Radio. (v0.8)\n---\n Created By Coolshrimp\n\nUp = Preset Up\nDown = Preset Down\nLeft = Seek Down \nRight = Seek Up \n OK = Toggle Mute");
+    view_set_previous_callback(widget_get_view(app->widget_about), fmradio_controller_navigation_submenu_callback);
+    view_dispatcher_add_view(app->view_dispatcher, FMRadioViewAbout, widget_get_view(app->widget_about));
+
+    app->notifications = furi_record_open(RECORD_NOTIFICATION);
+
+#ifdef BACKLIGHT_ALWAYS_ON
+    notification_message(app->notifications, &sequence_display_backlight_enforce_on);
+#endif
+
+    return app;
+}
+
+// Free memory used by the application
+void fmradio_controller_free(FMRadio* app) {
+#ifdef BACKLIGHT_ALWAYS_ON
+    notification_message(app->notifications, &sequence_display_backlight_enforce_auto);
+#endif
+    furi_record_close(RECORD_NOTIFICATION);
+
+    view_dispatcher_remove_view(app->view_dispatcher, FMRadioViewAbout);
+    widget_free(app->widget_about);
+    view_dispatcher_remove_view(app->view_dispatcher, FMRadioViewFlipTheWorld);
+    view_free(app->view_flip_the_world);
+    view_dispatcher_remove_view(app->view_dispatcher, FMRadioViewConfigure);
+    variable_item_list_free(app->variable_item_list_config);
+    view_dispatcher_remove_view(app->view_dispatcher, FMRadioViewSubmenu);
+    submenu_free(app->submenu);
+    view_dispatcher_free(app->view_dispatcher);
     furi_record_close(RECORD_GUI);
-    furi_message_queue_free(demo_context->queue);
-    furi_mutex_free(demo_context->mutex);
-    furi_string_free(demo_context->data->buffer);
-    free(demo_context->data);
-    free(demo_context);
 
+    free(app);
+}
+
+// Main function to start the application
+int32_t fmradio_controller_app(void* p) {
+    UNUSED(p);
+
+    FMRadio* app = fmradio_controller_alloc();
+    view_dispatcher_run(app->view_dispatcher);
+
+    fmradio_controller_free(app);
     return 0;
-}
+}