|
| 1 | +#include "pmrscan_app.h" |
| 2 | +#include <furi.h> |
| 3 | +#include <furi_hal.h> |
| 4 | +#include <furi_hal_gpio.h> |
| 5 | +#include <gui/elements.h> |
| 6 | +#include <furi_hal_speaker.h> |
| 7 | +#include <subghz/devices/devices.h> |
| 8 | + |
| 9 | +#define TAG "PMRScanApp" |
| 10 | + |
| 11 | +#define PMRSCAN_DEFAULT_RSSI (-100.0f) |
| 12 | +#define PMRSCAN_DEFAULT_SENSITIVITY (-85.0f) |
| 13 | + |
| 14 | +#define PMRSCAN_VOLUME_QUIET 0.0f |
| 15 | +#define PMRSCAN_VOLUME_LOUD 1.0f |
| 16 | + |
| 17 | +#define SUBGHZ_DEVICE_NAME "cc1101_int" |
| 18 | + |
| 19 | +const uint32_t freqs[] = { |
| 20 | + 0, |
| 21 | + 446006250, |
| 22 | + 446018750, |
| 23 | + 446031250, |
| 24 | + 446043750, |
| 25 | + 446056250, |
| 26 | + 446068750, |
| 27 | + 446081250, |
| 28 | + 446093750, |
| 29 | + 446106250, |
| 30 | + 446118750, |
| 31 | + 446131250, |
| 32 | + 446143750, |
| 33 | + 446156250, |
| 34 | + 446168750, |
| 35 | + 446181250, |
| 36 | + 446193750, |
| 37 | +}; |
| 38 | +const int num_channels = sizeof(freqs) / sizeof(freqs[0]); |
| 39 | +const int min_channel = 1; |
| 40 | +const int max_channel = num_channels - 1; |
| 41 | + |
| 42 | +static void speaker_mute(PMRScanApp* app) { |
| 43 | + if(app->speaker_acquired && furi_hal_speaker_is_mine()) { |
| 44 | + subghz_devices_set_async_mirror_pin(app->radio_device, NULL); |
| 45 | + furi_hal_speaker_set_volume(PMRSCAN_VOLUME_QUIET); |
| 46 | + } |
| 47 | +} |
| 48 | + |
| 49 | +static void speaker_loud(PMRScanApp* app) { |
| 50 | + if(app->speaker_acquired && furi_hal_speaker_is_mine()) { |
| 51 | + subghz_devices_set_async_mirror_pin(app->radio_device, &gpio_speaker); |
| 52 | + furi_hal_speaker_set_volume(PMRSCAN_VOLUME_LOUD); |
| 53 | + } |
| 54 | +} |
| 55 | + |
| 56 | +static void pmrscan_draw_callback(Canvas* canvas, void* context) { |
| 57 | + furi_assert(canvas); |
| 58 | + furi_assert(context); |
| 59 | + PMRScanApp* app = (PMRScanApp*)context; |
| 60 | + canvas_clear(canvas); |
| 61 | + canvas_set_font(canvas, FontPrimary); |
| 62 | + canvas_draw_str_aligned(canvas, 64, 2, AlignCenter, AlignTop, "PMRScan"); |
| 63 | + |
| 64 | + canvas_set_font(canvas, FontSecondary); |
| 65 | + #define BUFLEN 32 |
| 66 | + char buf[BUFLEN + 1] = ""; |
| 67 | + snprintf(buf, BUFLEN, "< Chan %ld (%.4f) >", app->freq_channel, (double)app->frequency / 1000000); |
| 68 | + canvas_draw_str_aligned(canvas, 64, 18, AlignCenter, AlignTop, buf); |
| 69 | + |
| 70 | + snprintf(buf, BUFLEN, "RSSI: %.2f", (double)app->rssi); |
| 71 | + canvas_draw_str_aligned(canvas, 64, 30, AlignCenter, AlignTop, buf); |
| 72 | + |
| 73 | + snprintf(buf, BUFLEN, "^ Sens: %.2f V", (double)app->sensitivity); |
| 74 | + canvas_draw_str_aligned(canvas, 64, 42, AlignCenter, AlignTop, buf); |
| 75 | + |
| 76 | + canvas_draw_str_aligned(canvas, 64, 54, AlignCenter, AlignTop, app->scanning ? "Scanning ()" : "Locked ()"); |
| 77 | +} |
| 78 | + |
| 79 | +static void pmrscan_input_callback(InputEvent* input_event, void* context) { |
| 80 | + furi_assert(context); |
| 81 | + FuriMessageQueue* event_queue = context; |
| 82 | + furi_message_queue_put(event_queue, input_event, FuriWaitForever); |
| 83 | + FURI_LOG_D(TAG, "Exit pmrscan_input_callback"); |
| 84 | +} |
| 85 | + |
| 86 | +static void pmrscan_rx_callback(const void* data, size_t size, void* context) { |
| 87 | + UNUSED(data); |
| 88 | + UNUSED(context); |
| 89 | + UNUSED(size); |
| 90 | +} |
| 91 | + |
| 92 | +static void pmrscan_update_rssi(PMRScanApp* app) { |
| 93 | + furi_assert(app); |
| 94 | + if(app->radio_device) { |
| 95 | + app->rssi = subghz_devices_get_rssi(app->radio_device); |
| 96 | + } else { |
| 97 | + FURI_LOG_E(TAG, "Radio device is NULL"); |
| 98 | + app->rssi = PMRSCAN_DEFAULT_RSSI; |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +static bool pmrscan_init_subghz(PMRScanApp* app) { |
| 103 | + furi_assert(app); |
| 104 | + subghz_devices_init(); |
| 105 | + |
| 106 | + const SubGhzDevice* device = subghz_devices_get_by_name(SUBGHZ_DEVICE_NAME); |
| 107 | + if(!device) { |
| 108 | + FURI_LOG_E(TAG, "Failed to get SubGhzDevice"); |
| 109 | + return false; |
| 110 | + } |
| 111 | + FURI_LOG_I(TAG, "SubGhzDevice obtained: %s", subghz_devices_get_name(device)); |
| 112 | + |
| 113 | + app->radio_device = device; |
| 114 | + |
| 115 | + subghz_devices_begin(device); |
| 116 | + subghz_devices_reset(device); |
| 117 | + if(!subghz_devices_is_frequency_valid(device, app->frequency)) { |
| 118 | + FURI_LOG_E(TAG, "Invalid frequency: %lu", app->frequency); |
| 119 | + return false; |
| 120 | + } |
| 121 | + subghz_devices_load_preset(device, FuriHalSubGhzPreset2FSKDev238Async, NULL); |
| 122 | + subghz_devices_set_frequency(device, app->frequency); |
| 123 | + subghz_devices_start_async_rx(device, pmrscan_rx_callback, app); |
| 124 | + if(furi_hal_speaker_acquire(30)) { |
| 125 | + app->speaker_acquired = true; |
| 126 | + speaker_mute(app); |
| 127 | + } else { |
| 128 | + app->speaker_acquired = false; |
| 129 | + FURI_LOG_E(TAG, "Failed to acquire speaker"); |
| 130 | + } |
| 131 | + return true; |
| 132 | +} |
| 133 | + |
| 134 | +static uint32_t pmrscan_next_channel(PMRScanApp* app) { |
| 135 | + uint32_t channel = app->freq_channel; |
| 136 | + |
| 137 | + if(app->scan_dir == ScanDirDown) |
| 138 | + channel -= 1; |
| 139 | + else |
| 140 | + channel += 1; |
| 141 | + |
| 142 | + if(channel < min_channel) |
| 143 | + channel = max_channel; |
| 144 | + else if(channel > max_channel) |
| 145 | + channel = min_channel; |
| 146 | + |
| 147 | + return channel; |
| 148 | +} |
| 149 | + |
| 150 | +static void pmrscan_process_scanning(PMRScanApp* app) { |
| 151 | + furi_assert(app); |
| 152 | + pmrscan_update_rssi(app); |
| 153 | + bool signal_detected = (app->rssi > app->sensitivity); |
| 154 | + |
| 155 | + if(signal_detected) { |
| 156 | + if(app->scanning) { |
| 157 | + app->scanning = false; |
| 158 | + } |
| 159 | + } else { |
| 160 | + if(!app->scanning) { |
| 161 | + app->scanning = true; |
| 162 | + } |
| 163 | + } |
| 164 | + |
| 165 | + if(app->scanning) { |
| 166 | + speaker_mute(app); |
| 167 | + } else { |
| 168 | + speaker_loud(app); |
| 169 | + } |
| 170 | + |
| 171 | + uint32_t new_channel = pmrscan_next_channel(app); |
| 172 | + uint32_t new_frequency = freqs[new_channel]; |
| 173 | + |
| 174 | + if(!subghz_devices_is_frequency_valid(app->radio_device, new_frequency)) { |
| 175 | + new_channel = min_channel; |
| 176 | + new_frequency = freqs[new_channel]; |
| 177 | + } |
| 178 | + |
| 179 | + subghz_devices_flush_rx(app->radio_device); |
| 180 | + subghz_devices_stop_async_rx(app->radio_device); |
| 181 | + |
| 182 | + subghz_devices_idle(app->radio_device); |
| 183 | + app->freq_channel = new_channel; |
| 184 | + app->frequency = new_frequency; |
| 185 | + subghz_devices_set_frequency(app->radio_device, app->frequency); |
| 186 | + |
| 187 | + subghz_devices_start_async_rx(app->radio_device, pmrscan_rx_callback, app); |
| 188 | +} |
| 189 | + |
| 190 | +PMRScanApp* pmrscan_app_alloc() { |
| 191 | + PMRScanApp* app = malloc(sizeof(PMRScanApp)); |
| 192 | + if(!app) { |
| 193 | + FURI_LOG_E(TAG, "Failed to allocate PMRScanApp"); |
| 194 | + return NULL; |
| 195 | + } |
| 196 | + |
| 197 | + app->view_port = view_port_alloc(); |
| 198 | + |
| 199 | + app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); |
| 200 | + |
| 201 | + app->running = true; |
| 202 | + app->freq_channel = min_channel; |
| 203 | + app->frequency = freqs[app->freq_channel]; |
| 204 | + app->rssi = PMRSCAN_DEFAULT_RSSI; |
| 205 | + app->sensitivity = PMRSCAN_DEFAULT_SENSITIVITY; |
| 206 | + app->scanning = true; |
| 207 | + app->scan_dir = ScanDirUp; |
| 208 | + app->speaker_acquired = false; |
| 209 | + app->radio_device = NULL; |
| 210 | + |
| 211 | + view_port_draw_callback_set(app->view_port, pmrscan_draw_callback, app); |
| 212 | + |
| 213 | + view_port_input_callback_set(app->view_port, pmrscan_input_callback, app->event_queue); |
| 214 | + |
| 215 | + app->gui = furi_record_open(RECORD_GUI); |
| 216 | + |
| 217 | + gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); |
| 218 | + |
| 219 | + return app; |
| 220 | +} |
| 221 | + |
| 222 | +void pmrscan_app_free(PMRScanApp* app) { |
| 223 | + furi_assert(app); |
| 224 | + if(app->speaker_acquired && furi_hal_speaker_is_mine()) { |
| 225 | + subghz_devices_set_async_mirror_pin(app->radio_device, NULL); |
| 226 | + furi_hal_speaker_set_volume(PMRSCAN_VOLUME_LOUD); |
| 227 | + furi_hal_speaker_release(); |
| 228 | + app->speaker_acquired = false; |
| 229 | + } |
| 230 | + |
| 231 | + if(app->radio_device) { |
| 232 | + subghz_devices_flush_rx(app->radio_device); |
| 233 | + subghz_devices_stop_async_rx(app->radio_device); |
| 234 | + subghz_devices_idle(app->radio_device); |
| 235 | + subghz_devices_sleep(app->radio_device); |
| 236 | + subghz_devices_end(app->radio_device); |
| 237 | + } |
| 238 | + |
| 239 | + subghz_devices_deinit(); |
| 240 | + gui_remove_view_port(app->gui, app->view_port); |
| 241 | + view_port_free(app->view_port); |
| 242 | + |
| 243 | + furi_message_queue_free(app->event_queue); |
| 244 | + |
| 245 | + furi_record_close(RECORD_GUI); |
| 246 | + |
| 247 | + free(app); |
| 248 | +} |
| 249 | + |
| 250 | +int32_t pmrscan_app(void* p) { |
| 251 | + UNUSED(p); |
| 252 | + FURI_LOG_I(TAG, "Enter pmrscan_app"); |
| 253 | + |
| 254 | + PMRScanApp* app = pmrscan_app_alloc(); |
| 255 | + if(!app) { |
| 256 | + FURI_LOG_E(TAG, "Failed to allocate app"); |
| 257 | + return 1; |
| 258 | + } |
| 259 | + |
| 260 | + if(!pmrscan_init_subghz(app)) { |
| 261 | + FURI_LOG_E(TAG, "Failed to initialize SubGHz"); |
| 262 | + pmrscan_app_free(app); |
| 263 | + return 255; |
| 264 | + } |
| 265 | + |
| 266 | + InputEvent event; |
| 267 | + while(app->running) { |
| 268 | + if(app->scanning) { |
| 269 | + speaker_mute(app); |
| 270 | + pmrscan_process_scanning(app); |
| 271 | + } else { |
| 272 | + speaker_loud(app); |
| 273 | + pmrscan_update_rssi(app); |
| 274 | + } |
| 275 | + |
| 276 | + if(furi_message_queue_get(app->event_queue, &event, 10) == FuriStatusOk) { |
| 277 | + if(event.type == InputTypeShort) { |
| 278 | + if(event.key == InputKeyOk) { |
| 279 | + app->scanning = !app->scanning; |
| 280 | + FURI_LOG_I(TAG, "Toggled scanning: %d", app->scanning); |
| 281 | + } else if(event.key == InputKeyUp) { |
| 282 | + app->sensitivity += 1.0f; |
| 283 | + FURI_LOG_I(TAG, "Increased sensitivity: %f", (double)app->sensitivity); |
| 284 | + } else if(event.key == InputKeyDown) { |
| 285 | + app->sensitivity -= 1.0f; |
| 286 | + FURI_LOG_I(TAG, "Decreased sensitivity: %f", (double)app->sensitivity); |
| 287 | + } else if(event.key == InputKeyLeft) { |
| 288 | + app->scan_dir = ScanDirDown; |
| 289 | + FURI_LOG_I(TAG, "Scan direction set to down"); |
| 290 | + } else if(event.key == InputKeyRight) { |
| 291 | + app->scan_dir = ScanDirUp; |
| 292 | + FURI_LOG_I(TAG, "Scan direction set to up"); |
| 293 | + } else if(event.key == InputKeyBack) { |
| 294 | + app->running = false; |
| 295 | + FURI_LOG_I(TAG, "Exiting app"); |
| 296 | + } |
| 297 | + } |
| 298 | + } |
| 299 | + |
| 300 | + view_port_update(app->view_port); |
| 301 | + furi_delay_ms(500); |
| 302 | + } |
| 303 | + |
| 304 | + pmrscan_app_free(app); |
| 305 | + return 0; |
| 306 | +} |
0 commit comments