From e0fd8fede8cde94e1e59ba7f0a79ee9a3d1d42ef Mon Sep 17 00:00:00 2001 From: UberGuidoZ <57457139+UberGuidoZ@users.noreply.github.com> Date: Tue, 24 Mar 2026 20:14:57 -0700 Subject: [PATCH] Added Smart Meter Monitor v1.11 --- .../UberGuidoZ/smart_meter_monitor/README.md | 148 ++++++ .../Release/smart_meter_monitor.fap | Bin 0 -> 15868 bytes .../Screenshots/01 Name.png | Bin 0 -> 664 bytes .../Screenshots/02 About.png | Bin 0 -> 5474 bytes .../Screenshots/03 Main.png | Bin 0 -> 5114 bytes .../Screenshots/04 Instructions 1.png | Bin 0 -> 7047 bytes .../Screenshots/05 Instructions 2.png | Bin 0 -> 6961 bytes .../Screenshots/06 Instructions 3.png | Bin 0 -> 6928 bytes .../Screenshots/07 Instructions 4.png | Bin 0 -> 6373 bytes .../Screenshots/08 Monitoring.png | Bin 0 -> 5256 bytes .../Screenshots/09 Detecting.png | Bin 0 -> 5449 bytes .../Source/application.fam | 11 + .../smart_meter_monitor/Source/scene_about.c | 66 +++ .../smart_meter_monitor/Source/scene_freq.c | 50 ++ .../Source/scene_instructions.c | 189 +++++++ .../smart_meter_monitor/Source/scene_menu.c | 61 +++ .../Source/scene_monitor.c | 488 ++++++++++++++++++ .../smart_meter_monitor/Source/scene_splash.c | 114 ++++ .../smart_meter_monitor/Source/smart_meter.h | 251 +++++++++ .../Source/smart_meter_app.c | 189 +++++++ .../Source/smart_meter_icon.png | Bin 0 -> 371 bytes 21 files changed, 1567 insertions(+) create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/README.md create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Release/smart_meter_monitor.fap create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Screenshots/01 Name.png create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Screenshots/02 About.png create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Screenshots/03 Main.png create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Screenshots/04 Instructions 1.png create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Screenshots/05 Instructions 2.png create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Screenshots/06 Instructions 3.png create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Screenshots/07 Instructions 4.png create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Screenshots/08 Monitoring.png create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Screenshots/09 Detecting.png create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Source/application.fam create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Source/scene_about.c create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Source/scene_freq.c create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Source/scene_instructions.c create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Source/scene_menu.c create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Source/scene_monitor.c create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Source/scene_splash.c create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Source/smart_meter.h create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Source/smart_meter_app.c create mode 100644 Applications/UberGuidoZ/smart_meter_monitor/Source/smart_meter_icon.png diff --git a/Applications/UberGuidoZ/smart_meter_monitor/README.md b/Applications/UberGuidoZ/smart_meter_monitor/README.md new file mode 100644 index 000000000..aaa56d355 --- /dev/null +++ b/Applications/UberGuidoZ/smart_meter_monitor/README.md @@ -0,0 +1,148 @@ +# Smart Meter Monitor - Flipper Zero FAP v1.11 + +By: UberGuidoZ | https://github.com/UberGuidoZ/Flipper + +Code: [Applications/UberGuidoZ/smart_meter_monitor/Source](https://github.com/UberGuidoZ/Flipper/tree/main/Applications/UberGuidoZ/smart_meter_monitor/Source) + +Monitors the 433 MHz, 868 MHz, and 915 MHz bands for smart meter +radio transmissions using the Flipper Zero's built-in CC1101 +sub-GHz radio. Displays live RSSI as a scrolling waveform, +detects signal bursts, and tracks transmission intervals. + +--- + +## What It Does + +Most North American electric and gas meters use the ERT (Encoder +Receiver Transmitter) protocol at 915 MHz, broadcasting +unencrypted SCM (Standard Consumption Message) packets every +30-90 seconds. European meters typically use 868 MHz. Some water +meters use 433 MHz. + +This app monitors RSSI (signal strength) in real time. When a +meter transmits, a spike appears in the waveform and is counted +as a detection event. The app tracks how often transmissions +occur, giving you the broadcast interval of meters in range. + +No packet decoding is performed - this is energy detection only. + +--- + +## Display + +``` +Meter Monitor 915MHz 500ms +[RSSI waveform - 128 samples, newest on right] +[dashed threshold line across waveform] +-90dBm Cnt:003 +Int: 47s Avg: 52s +``` + +- Waveform bars show RSSI from -120 dBm (bottom) to -40 dBm (top) +- Dashed line marks the burst detection threshold +- Cnt: total detected transmissions since last reset +- Int: interval between last two detections (seconds) +- Avg: exponential moving average of all intervals + +When fewer than 2 bursts have been detected, line 2 shows the +current threshold and "---" for interval. + +--- + +## Controls + +| Button | Action | +|-------------|----------------------------------------| +| OK (short) | Pause / resume capture (II indicator) | +| OK (long) | Reset stats and waveform history | +| UP | Raise threshold (less sensitive) | +| DOWN | Lower threshold (more sensitive) | +| LEFT | Previous frequency (cycle 433/868/915) | +| RIGHT | Next frequency | +| BACK | Stop radio, return to menu | + +--- + +## Frequencies + +| Frequency | Region | Meter type | +|------------|----------------|----------------------| +| 433.92 MHz | Worldwide | Some water meters | +| 868.30 MHz | Europe | Electric / gas | +| 915.00 MHz | North America | Electric / gas (ERT) | + +Change the frequency on the fly with LEFT/RIGHT while monitoring. +NOTE: Changing frequency resets stats and restarts the radio. + +--- + +## Sample Rates and History Window + +| Rate | History at 128 samples | +|--------|------------------------| +| 100 ms | 13 seconds | +| 250 ms | 32 seconds | +| 500 ms | 64 seconds (default) | +| 1 s | 128 seconds (~2 min) | +| 2 s | 256 seconds (~4 min) | + +UP/DOWN cycles the sample rate while monitoring. + +--- + +## Threshold Adjustment + +The dashed line on the waveform marks the burst detection +threshold. Adjust with UP/DOWN in 5 dBm steps (-110 to -60 dBm). + +Default: -90 dBm. Start here and watch the ambient RSSI noise +floor. Raise the threshold until it sits clearly above the noise +floor but below the meter transmission spikes. + +Typical noise floor: -100 to -105 dBm +Typical meter transmission RSSI: -60 to -90 dBm (distance-dependent) + +--- + +## Building + +```sh +pip install --upgrade ufbt +ufbt update +cd smart_meter_monitor +ufbt launch +``` + +Output: `dist/smart_meter_monitor.fap` +Install to: `/ext/apps/Sub-GHz/smart_meter_monitor.fap` + +--- + +## File Structure + +``` +smart_meter_monitor/ + application.fam - ufbt app manifest + smart_meter.h - shared types, constants, app struct + smart_meter_app.c - entry point, alloc/free, scene handler table + scene_splash.c - splash screen (3s auto-advance) + scene_menu.c - main menu + scene_freq.c - frequency selector submenu + scene_monitor.c - CC1101 capture, waveform, burst detection + scene_instructions.c - 4-page on-screen reference + scene_about.c - about screen + README.md - this file +``` + +--- + +## Notes + +- The app uses the CC1101 HAL directly. Do not launch while the + built-in Sub-GHz app is actively scanning. +- Burst detection uses hysteresis: 2 consecutive samples above + threshold to enter, 3 consecutive samples below to exit. This + prevents spurious counts from momentary noise spikes. +- The ERT protocol broadcasts every 30-90 seconds depending on + meter model and utility configuration. Allow 2-3 minutes of + monitoring before drawing conclusions from interval data. \ No newline at end of file diff --git a/Applications/UberGuidoZ/smart_meter_monitor/Release/smart_meter_monitor.fap b/Applications/UberGuidoZ/smart_meter_monitor/Release/smart_meter_monitor.fap new file mode 100644 index 0000000000000000000000000000000000000000..b50a04ea9d036590ceb8f23b52caa635d0d3a60a GIT binary patch literal 15868 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 Kb6Mw<&;$UKDF)^M literal 0 HcmV?d00001 diff --git a/Applications/UberGuidoZ/smart_meter_monitor/Screenshots/02 About.png b/Applications/UberGuidoZ/smart_meter_monitor/Screenshots/02 About.png new file mode 100644 index 0000000000000000000000000000000000000000..3c6fdd2f074e6954baf73dc391b512b8f5da20c6 GIT binary patch literal 5474 zcmY*d30PBS*8UPU1+lJ0MT$=AUWH1hpkP8pMMcF{Llh!O9VHYOP}Hyl61v#BARR3N z6%v#R){q26WKoh#JLpuAlwScsAXFqlf&_>mglu!}z3Dv9|2%m_a_)D&bKdiw_nga_ zojbxk-RHR@ggm!w-taj>WcV)$O>%`F4L>xo5t@d!Z1{9{3Td#j>*$3~wck0fH)hXn$mhTXW z>^x~&{Nmx!{V1N(H*aw$AHH%$I)`D&aCe2>t!LlA-f=w0Lh+~m>{tYkWet(-Z&YZQ z>1N-K9deGIJO@SZtB#vIZ9!=9J%xQD5LqIBwa-|9J-mtR%HNbHreW9huNscDyYhJ- zTF&Cv+?no=?$jn^V^4;1vU056h>|e=cjCss!Hvh8J3b(8l#eB%@s_X+#D$aKLPq6c z;=)icZJz!SacTjaO8PzeuTw@i)%9;31O}nJahZZ%FmJg!;uK$Bj?Gc!gOp zHi<|=Xa-|@gN{f-48|CyRP8XTu#?*#=w{+4v%%=il(hb3#7=fSaL40$9;HAPZ(0)0 zWBJy3a9ic1o&cXifW?AP)-IBhqSQ;Y(g95>K%>kkCvIGc-54t>C7gz~$VtUB>QbzR zhcC|gx^|Ovq2FyA)vgs-N`LI@(LxPnNBg#5IW4Hj=95@gM9(H+Se)>h z0|EG7>)T7%&VcRGxo55pn-*;9zAp*ew_rPW=$W&_25j?Ru1+OvSz!A*;u+at(}C@1 zoE!Grv^}wawq92bE4M>hqfUCUz3!S*bKAPCbipjQRfy%?L-)OXt*;ErZ*H7prU6k}TGiQhy z5mDu=e%Bv0W>9awkB7^f9yIpCmH&^-Qv}Nv0U;t{uUvDGw;3V~cL`K6Gb*ivx_45T z5rXAR37gR;5-6>6HMZqV*uM$&X|fKX_TIzSw1(IJRBF;5gbOiq>RdYZOF?93i~{{( zww;W`Ad7IH@7IBnI(NDfNEZUqwJUb{6G#V@KoWD$dT3JYJM1H635(>*;uvp~hEDsf z%SNWqYKfN_=TU6C&B^O}2Ls!v0)tJ

x#*a;UYLCmDua;+$4`pE-WSrfn3ORYKGF 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 literal 0 HcmV?d00001 diff --git a/Applications/UberGuidoZ/smart_meter_monitor/Screenshots/03 Main.png b/Applications/UberGuidoZ/smart_meter_monitor/Screenshots/03 Main.png new file mode 100644 index 0000000000000000000000000000000000000000..ac2a489d4109660e97d2866f60288ffaac5f42e6 GIT binary patch literal 5114 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 literal 0 HcmV?d00001 diff --git a/Applications/UberGuidoZ/smart_meter_monitor/Screenshots/05 Instructions 2.png b/Applications/UberGuidoZ/smart_meter_monitor/Screenshots/05 Instructions 2.png new file mode 100644 index 0000000000000000000000000000000000000000..b5fad2f53878158b2f3fe055589c3b9bac4d1139 GIT binary patch literal 6961 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 literal 0 HcmV?d00001 diff --git a/Applications/UberGuidoZ/smart_meter_monitor/Screenshots/07 Instructions 4.png b/Applications/UberGuidoZ/smart_meter_monitor/Screenshots/07 Instructions 4.png new file mode 100644 index 0000000000000000000000000000000000000000..fc6a0c72909e8bf6d58f167b24f995db8e898808 GIT binary patch literal 6373 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 bi6=m1>koZ@He?Na2W;K6>%*!I+`s-eri9`7 literal 0 HcmV?d00001 diff --git a/Applications/UberGuidoZ/smart_meter_monitor/Screenshots/08 Monitoring.png b/Applications/UberGuidoZ/smart_meter_monitor/Screenshots/08 Monitoring.png new file mode 100644 index 0000000000000000000000000000000000000000..7a7b50a47c3bcff115e4fa953f7a0aa1e97178c8 GIT binary patch literal 5256 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 literal 0 HcmV?d00001 diff --git a/Applications/UberGuidoZ/smart_meter_monitor/Screenshots/09 Detecting.png b/Applications/UberGuidoZ/smart_meter_monitor/Screenshots/09 Detecting.png new file mode 100644 index 0000000000000000000000000000000000000000..a498f8318f90af86b95161748b861dea06de3144 GIT binary patch literal 5449 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 w2 ? w1 : w2) + 16U; + int32_t box_x = (int32_t)((128U - box_w) / 2U); + if(box_x < 1) box_x = 1; + canvas_draw_frame(canvas, box_x, 8, (uint8_t)box_w, 32); + canvas_draw_str(canvas, (int32_t)((128U - w1) / 2U), 22, "Smart Meter"); + canvas_draw_str(canvas, (int32_t)((128U - w2) / 2U), 34, "Monitor"); + + // ---------------------------------------------------------- + // Separator and byline. + // ---------------------------------------------------------- + canvas_draw_line(canvas, 10, 44, 118, 44); + canvas_set_font(canvas, FontKeyboard); + canvas_draw_str(canvas, 7, 54, "v1.11 by UberGuidoZ"); + canvas_draw_str(canvas, 1, 62, "github.com/UberGuidoZ"); +} + +static bool sm_about_input_cb(InputEvent* event, void* context) { + UNUSED(event); + UNUSED(context); + return false; +} + +void scene_about_setup_view(SmartMeterApp* app) { + furi_check(app != NULL); + + app->view_about = view_alloc(); + furi_check(app->view_about != NULL); + + view_set_draw_callback(app->view_about, sm_about_draw_cb); + view_set_input_callback(app->view_about, sm_about_input_cb); +} + +void scene_about_on_enter(void* context) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + view_dispatcher_switch_to_view(app->view_dispatcher, SmViewAbout); +} + +bool scene_about_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void scene_about_on_exit(void* context) { + UNUSED(context); +} \ No newline at end of file diff --git a/Applications/UberGuidoZ/smart_meter_monitor/Source/scene_freq.c b/Applications/UberGuidoZ/smart_meter_monitor/Source/scene_freq.c new file mode 100644 index 000000000..cf821b94a --- /dev/null +++ b/Applications/UberGuidoZ/smart_meter_monitor/Source/scene_freq.c @@ -0,0 +1,50 @@ +// #################################################### +// # scene_freq.c - frequency selector submenu # +// # By: UberGuidoZ | https://github.com/UberGuidoZ/ # +// # v1.11 # +// #################################################### + +#include "smart_meter.h" + +// Frequencies must stay in sync with k_frequencies[] in scene_monitor.c. +static const char* const k_freq_menu_labels[SM_FREQ_COUNT] = { + "433.92 MHz (some water)", + "868.30 MHz (EU meters)", + "915.00 MHz (US meters)", +}; + +static void sm_freq_cb(void* context, uint32_t index) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + app->freq_index = (uint8_t)index; + // Pop back to the menu after selection. + scene_manager_previous_scene(app->scene_manager); +} + +void scene_freq_on_enter(void* context) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + + submenu_reset(app->submenu_freq); + submenu_set_header(app->submenu_freq, "Select Frequency"); + + for(uint8_t i = 0U; i < SM_FREQ_COUNT; i++) { + submenu_add_item(app->submenu_freq, k_freq_menu_labels[i], i, sm_freq_cb, app); + } + // Highlight the currently selected frequency. + submenu_set_selected_item(app->submenu_freq, app->freq_index); + + view_dispatcher_switch_to_view(app->view_dispatcher, SmViewFreq); +} + +bool scene_freq_on_event(void* context, SceneManagerEvent event) { + UNUSED(context); + UNUSED(event); + return false; +} + +void scene_freq_on_exit(void* context) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + submenu_reset(app->submenu_freq); +} \ No newline at end of file diff --git a/Applications/UberGuidoZ/smart_meter_monitor/Source/scene_instructions.c b/Applications/UberGuidoZ/smart_meter_monitor/Source/scene_instructions.c new file mode 100644 index 000000000..14d16e292 --- /dev/null +++ b/Applications/UberGuidoZ/smart_meter_monitor/Source/scene_instructions.c @@ -0,0 +1,189 @@ +// ###################################################### +// # scene_instructions.c - 4-page on-screen reference # +// # By: UberGuidoZ | https://github.com/UberGuidoZ/ # +// # v1.11 # +// ###################################################### +// +// Page 1: About Smart Meters +// Page 2: Frequencies +// Page 3: Display Guide +// Page 4: Controls +// +// LEFT - previous page +// RIGHT - next page +// BACK - return to menu +// +// ############################################################## + +#include "smart_meter.h" +#include +#include + +static const char* const k_page_titles[SM_INSTR_PAGES] = { + "About Smart Meters", + "Frequencies", + "Display Guide", + "Controls", +}; + +// Max 21 chars per line (FontKeyboard at 6px/char = 126px) +static const char* const k_page_body[SM_INSTR_PAGES][SM_INSTR_BODY_LINES] = { + // Page 0: About Smart Meters + { + "Meters broadcast ERT", + "packets every 30-60s", + "CC1101 detects RSSI", + "spikes from each TX", + "No decoding needed", + }, + // Page 1: Frequencies + { + "433MHz some water", + "868.30MHz EU electric", + "915.00MHz US electric", + "LEFT/RIGHT to change", + "Changing freq resets", + }, + // Page 2: Display Guide + { + "Bars = RSSI strength", + "Dashed line=threshold", + "Spike = transmission", + "C=count /m=per min", + "~SCM=brief INT?=noise", + }, + // Page 3: Controls + { + "OK pause/resume", + "OK hold reset stats", + "UP/DOWN threshold", + "Hold U/D rate change", + "L/R frequency", + }, +}; + +// ============================================================ +// Draw callback +// ============================================================ +static void sm_instructions_draw_cb(Canvas* canvas, void* model) { + SmInstrModel* m = (SmInstrModel*)model; + if(m == NULL) return; + + uint8_t page = m->page; + if(page >= SM_INSTR_PAGES) page = 0U; + + canvas_clear(canvas); + + // ---------------------------------------------------------- + // Title bar with page indicator right-aligned (same baseline). + // ---------------------------------------------------------- + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 1, 7, k_page_titles[page]); + + char indicator[8]; + snprintf(indicator, sizeof(indicator), "%d/%d", (int)(page + 1U), (int)SM_INSTR_PAGES); + size_t iw = canvas_string_width(canvas, indicator); + canvas_draw_str(canvas, (int32_t)(127U - iw), 7, indicator); + + canvas_draw_line(canvas, 0, 9, 127, 9); + + // ---------------------------------------------------------- + // Body text: 5 lines, 10px spacing (8px glyph + 2px gap). + // Lines at baselines y=19,29,39,49,59. + // ---------------------------------------------------------- + canvas_set_font(canvas, FontKeyboard); + int32_t y = 19; + for(uint8_t i = 0U; i < SM_INSTR_BODY_LINES; i++) { + if(k_page_body[page][i] != NULL && k_page_body[page][i][0] != '\0') { + canvas_draw_str(canvas, 1, y, k_page_body[page][i]); + } + y += 10; + } +} + +// ============================================================ +// Input callback +// ============================================================ +static bool sm_instructions_input_cb(InputEvent* event, void* context) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + + if(event->type == InputTypeShort) { + switch(event->key) { + case InputKeyLeft: + view_dispatcher_send_custom_event( + app->view_dispatcher, SmEventInstrPageLeft); + return true; + case InputKeyRight: + view_dispatcher_send_custom_event( + app->view_dispatcher, SmEventInstrPageRight); + return true; + default: + break; + } + } + return false; +} + +// ============================================================ +// View setup +// ============================================================ +void scene_instructions_setup_view(SmartMeterApp* app) { + furi_check(app != NULL); + + app->view_instructions = view_alloc(); + furi_check(app->view_instructions != NULL); + + view_set_draw_callback(app->view_instructions, sm_instructions_draw_cb); + view_set_input_callback(app->view_instructions, sm_instructions_input_cb); + view_set_context(app->view_instructions, app); + + view_allocate_model( + app->view_instructions, ViewModelTypeLockFree, sizeof(SmInstrModel)); + + SmInstrModel* m = view_get_model(app->view_instructions); + if(m != NULL) m->page = 0U; + view_commit_model(app->view_instructions, false); +} + +// ============================================================ +// Scene handlers +// ============================================================ +void scene_instructions_on_enter(void* context) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + + SmInstrModel* m = view_get_model(app->view_instructions); + if(m != NULL) m->page = 0U; + view_commit_model(app->view_instructions, false); + + view_dispatcher_switch_to_view(app->view_dispatcher, SmViewInstructions); +} + +bool scene_instructions_on_event(void* context, SceneManagerEvent event) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + + if(event.type != SceneManagerEventTypeCustom) return false; + + SmInstrModel* m = view_get_model(app->view_instructions); + if(m == NULL) return false; + + switch(event.event) { + case SmEventInstrPageLeft: + if(m->page > 0U) m->page--; + view_commit_model(app->view_instructions, true); + return true; + case SmEventInstrPageRight: + if(m->page < SM_INSTR_PAGES - 1U) m->page++; + view_commit_model(app->view_instructions, true); + return true; + default: + break; + } + return false; +} + +void scene_instructions_on_exit(void* context) { + UNUSED(context); +} \ No newline at end of file diff --git a/Applications/UberGuidoZ/smart_meter_monitor/Source/scene_menu.c b/Applications/UberGuidoZ/smart_meter_monitor/Source/scene_menu.c new file mode 100644 index 000000000..1ac10263d --- /dev/null +++ b/Applications/UberGuidoZ/smart_meter_monitor/Source/scene_menu.c @@ -0,0 +1,61 @@ +// #################################################### +// # scene_menu.c - main menu scene # +// # By: UberGuidoZ | https://github.com/UberGuidoZ/ # +// # v1.11 # +// #################################################### + +#include "smart_meter.h" + +static void sm_menu_cb(void* context, uint32_t index) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + view_dispatcher_send_custom_event(app->view_dispatcher, index); +} + +void scene_menu_on_enter(void* context) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + + submenu_reset(app->submenu_menu); + submenu_set_header(app->submenu_menu, "Smart Meter Monitor"); + + submenu_add_item(app->submenu_menu, "Start Monitor", SmEventMenuStart, sm_menu_cb, app); + submenu_add_item(app->submenu_menu, "Instructions", SmEventMenuInstr, sm_menu_cb, app); + submenu_add_item(app->submenu_menu, "About", SmEventMenuAbout, sm_menu_cb, app); + + view_dispatcher_switch_to_view(app->view_dispatcher, SmViewMenu); +} + +bool scene_menu_on_event(void* context, SceneManagerEvent event) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + + // BACK on the root menu exits the app entirely. + if(event.type == SceneManagerEventTypeBack) { + view_dispatcher_stop(app->view_dispatcher); + return true; + } + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case SmEventMenuStart: + scene_manager_next_scene(app->scene_manager, SmSceneMonitor); + return true; + case SmEventMenuInstr: + scene_manager_next_scene(app->scene_manager, SmSceneInstructions); + return true; + case SmEventMenuAbout: + scene_manager_next_scene(app->scene_manager, SmSceneAbout); + return true; + default: + break; + } + } + return false; +} + +void scene_menu_on_exit(void* context) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + submenu_reset(app->submenu_menu); +} \ No newline at end of file diff --git a/Applications/UberGuidoZ/smart_meter_monitor/Source/scene_monitor.c b/Applications/UberGuidoZ/smart_meter_monitor/Source/scene_monitor.c new file mode 100644 index 000000000..fb5da5368 --- /dev/null +++ b/Applications/UberGuidoZ/smart_meter_monitor/Source/scene_monitor.c @@ -0,0 +1,488 @@ +// ############################################################ +// # scene_monitor.c - CC1101 capture, waveform, burst stats # +// # By: UberGuidoZ | https://github.com/UberGuidoZ/ # +// # v1.11 # +// ############################################################ +// +// The CC1101 is polled by a FuriTimer at the selected sample +// rate. RSSI values fill a 128-sample circular buffer that maps +// 1:1 to the 128px-wide waveform (newest = rightmost pixel). +// A dashed threshold line across the waveform marks the burst +// detection level, adjustable in 5 dBm steps with UP/DOWN. +// +// CONTROLS: +// OK (short) - reset stats (count, intervals) +// OK (long) - pause / resume capture +// UP - raise detection threshold (less sensitive) +// DOWN - lower detection threshold (more sensitive) +// LEFT - cycle to previous frequency +// RIGHT - cycle to next frequency +// BACK - stop radio, return to menu +// +// ############################################################## + +#include "smart_meter.h" +#include +#include + +// ============================================================ +// Frequency and sample rate tables +// ============================================================ +static const uint32_t k_frequencies[SM_FREQ_COUNT] = { + 433920000U, // 433.92 MHz - some water meters + 868300000U, // 868.30 MHz - EU electric / gas + 915000000U, // 915.00 MHz - US electric / gas (ERT/SCM) +}; + +static const char* const k_freq_labels[SM_FREQ_COUNT] = { + "433MHz", + "868MHz", + "915MHz", +}; + +static const uint32_t k_sample_rates[SM_RATE_COUNT] = { + 100U, // 100 ms - 13s history + 250U, // 250 ms - 32s history + 500U, // 500 ms - 64s history (default) + 1000U, // 1000 ms - 128s history + 2000U, // 2000 ms - 256s history +}; + +static const char* const k_rate_labels[SM_RATE_COUNT] = { + "100ms", + "250ms", + "500ms", + "1s", + "2s", +}; + +// ============================================================ +// Radio helpers +// ============================================================ + +static void sm_radio_start(SmartMeterApp* app) { + furi_check(app != NULL); + if(app->radio_running) return; + + furi_hal_subghz_reset(); + furi_hal_subghz_set_frequency_and_path(k_frequencies[app->freq_index]); + furi_hal_subghz_rx(); + app->radio_running = true; +} + +static void sm_radio_stop(SmartMeterApp* app) { + furi_check(app != NULL); + if(!app->radio_running) return; + + furi_hal_subghz_idle(); + furi_hal_subghz_sleep(); + app->radio_running = false; +} + +// Restart radio on frequency change without clearing stats. +static void sm_radio_restart(SmartMeterApp* app) { + sm_radio_stop(app); + sm_radio_start(app); +} + +// ============================================================ +// Stats reset (does not touch the RSSI history or radio) +// ============================================================ +static void sm_reset_stats(SmartMeterApp* app) { + furi_check(app != NULL); + furi_mutex_acquire(app->mutex, FuriWaitForever); + app->burst_count = 0U; + app->last_burst_tick = 0U; + app->last_interval_ms = 0U; + app->avg_interval_ms = 0U; + app->burst_ts_head = 0U; + app->burst_ts_count = 0U; + app->tx_per_min = 0U; + app->burst_enter_tick = 0U; + app->burst_sample_count = 0U; + app->last_burst_samples = 0U; + app->in_burst = false; + app->burst_above = 0U; + app->burst_below = 0U; + // Clear the RSSI history so the waveform starts fresh. + memset(app->rssi_history, (uint8_t)SM_RSSI_MIN_DBM, sizeof(app->rssi_history)); + app->rssi_head = 0U; + app->rssi_count = 0U; + furi_mutex_release(app->mutex); +} + +// ============================================================ +// Timer callback (FreeRTOS timer service task) +// ============================================================ +static void sm_sample_timer_cb(void* context) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + + if(furi_mutex_acquire(app->mutex, 0U) != FuriStatusOk) return; + + if(app->radio_running && app->monitoring) { + // Read RSSI from CC1101. + float rssi_f = furi_hal_subghz_get_rssi(); + int8_t rssi; + if(rssi_f <= (float)SM_RSSI_MIN_DBM) rssi = (int8_t)SM_RSSI_MIN_DBM; + else if(rssi_f >= (float)SM_RSSI_MAX_DBM) rssi = (int8_t)SM_RSSI_MAX_DBM; + else rssi = (int8_t)rssi_f; + + app->rssi_current = rssi; + + // Write into circular buffer. + app->rssi_history[app->rssi_head] = rssi; + app->rssi_head = (app->rssi_head + 1U) % SM_HISTORY_SIZE; + if(app->rssi_count < SM_HISTORY_SIZE) app->rssi_count++; + + // Burst detection with hysteresis. + bool above = (rssi > app->threshold_dbm); + + if(!app->in_burst) { + // Not currently in a burst - look for entry condition. + if(above) { + app->burst_above++; + if(app->burst_above >= SM_BURST_ENTER_SAMPLES) { + app->in_burst = true; + app->burst_below = 0U; + app->burst_sample_count = app->burst_above; + app->burst_enter_tick = furi_get_tick(); + } + } else { + app->burst_above = 0U; + } + } else { + // In a burst - count samples and look for exit condition. + if(above) { + app->burst_sample_count++; + app->burst_below = 0U; + } else { + app->burst_below++; + if(app->burst_below >= SM_BURST_EXIT_SAMPLES) { + // Burst ended - record all stats. + app->in_burst = false; + app->burst_above = 0U; + app->last_burst_samples = app->burst_sample_count; + app->burst_sample_count = 0U; + + uint32_t now = furi_get_tick(); + + // Interval tracking. + if(app->burst_count > 0U && app->last_burst_tick > 0U) { + app->last_interval_ms = now - app->last_burst_tick; + if(app->avg_interval_ms == 0U) { + app->avg_interval_ms = app->last_interval_ms; + } else { + app->avg_interval_ms = + (app->avg_interval_ms * 3U + app->last_interval_ms) / 4U; + } + } + app->last_burst_tick = now; + app->burst_count++; + + // Tx/min: store timestamp in circular buffer. + app->burst_timestamps[app->burst_ts_head] = now; + app->burst_ts_head = + (uint8_t)((app->burst_ts_head + 1U) % 30U); + if(app->burst_ts_count < 30U) app->burst_ts_count++; + + // Count entries within the last 60 seconds. + uint8_t tpm = 0U; + for(uint8_t t = 0U; t < app->burst_ts_count; t++) { + if((now - app->burst_timestamps[t]) <= 60000U) tpm++; + } + app->tx_per_min = tpm; + + notification_message(app->notifications, &sequence_blink_green_10); + } + } + } + } + + furi_mutex_release(app->mutex); + view_commit_model(app->view_monitor, true); +} + +// ============================================================ +// Draw callback (GUI thread) +// ============================================================ +static void sm_monitor_draw_cb(Canvas* canvas, void* model) { + SmMonitorModel* m = (SmMonitorModel*)model; + if(m == NULL || m->app == NULL) return; + SmartMeterApp* app = m->app; + + // Always clear so stale pixels never persist on mutex timeout. + canvas_clear(canvas); + + if(furi_mutex_acquire(app->mutex, 25U) != FuriStatusOk) return; + + // ---------------------------------------------------------- + // Header bar + // ---------------------------------------------------------- + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 1, 7, "SMM"); + + // Right side: frequency, rate, and paused indicator. + char hdr[24]; + snprintf(hdr, sizeof(hdr), "%s %s%s", + k_freq_labels[app->freq_index], + k_rate_labels[app->rate_index], + app->monitoring ? "" : " II"); + size_t hw = canvas_string_width(canvas, hdr); + canvas_draw_str(canvas, (int32_t)(SM_SCREEN_W - 1U - hw), 7, hdr); + + // Separator + canvas_draw_line(canvas, 0, (int32_t)(SM_HEADER_H - 1U), + (int32_t)(SM_SCREEN_W - 1U), (int32_t)(SM_HEADER_H - 1U)); + + // ---------------------------------------------------------- + // RSSI waveform (bar graph, newest = rightmost column) + // ---------------------------------------------------------- + uint32_t count = app->rssi_count; + uint32_t head = app->rssi_head; + uint32_t show = count < SM_WAVE_W ? count : SM_WAVE_W; + uint32_t oldest_idx = count < SM_HISTORY_SIZE ? 0U : head; + + for(uint32_t s = 0U; s < show; s++) { + uint32_t x = SM_WAVE_W - show + s; + uint32_t sample_idx = (oldest_idx + s) % SM_HISTORY_SIZE; + int8_t rssi = app->rssi_history[sample_idx]; + + int32_t h = ((int32_t)rssi - SM_RSSI_MIN_DBM) * (int32_t)SM_WAVE_H / SM_RSSI_RANGE; + if(h < 0) h = 0; + if(h > (int32_t)SM_WAVE_H) h = (int32_t)SM_WAVE_H; + + if(h > 0) { + canvas_draw_line( + canvas, + (int32_t)x, (int32_t)SM_WAVE_Y_BOT, + (int32_t)x, (int32_t)SM_WAVE_Y_BOT - h + 1); + } + } + + // ---------------------------------------------------------- + // Threshold line (2-on, 2-off dashed across full width) + // ---------------------------------------------------------- + int32_t thr_h = ((int32_t)app->threshold_dbm - SM_RSSI_MIN_DBM) * + (int32_t)SM_WAVE_H / SM_RSSI_RANGE; + if(thr_h < 0) thr_h = 0; + if(thr_h > (int32_t)SM_WAVE_H) thr_h = (int32_t)SM_WAVE_H; + int32_t thr_y = (int32_t)SM_WAVE_Y_BOT - thr_h; + for(int32_t x = 0; x < (int32_t)SM_SCREEN_W; x += 4) { + canvas_draw_dot(canvas, x, thr_y); + canvas_draw_dot(canvas, x + 1, thr_y); + } + + // ---------------------------------------------------------- + // Stats line 1: RSSI, burst count, tx/min + // ---------------------------------------------------------- + canvas_set_font(canvas, FontKeyboard); + char s1[20]; + snprintf(s1, sizeof(s1), "%4ddBm C:%03lu %2u/m", + (int)app->rssi_current, + (unsigned long)app->burst_count, + (unsigned)app->tx_per_min); + canvas_draw_str(canvas, 0, (int32_t)SM_STATS1_Y, s1); + + // ---------------------------------------------------------- + // Stats line 2: intervals + SCM framing hint + // + // SCM heuristic: brief above-threshold events (1-2 samples) + // are consistent with short meter packets. Sustained events + // (4+ samples at any poll rate) suggest interference. + // This is energy-based estimation only, not actual decoding. + // ---------------------------------------------------------- + char s2[28]; + if(app->burst_count < 2U) { + snprintf(s2, sizeof(s2), "Thr:%3ddBm", (int)app->threshold_dbm); + } else { + uint32_t lst_s = app->last_interval_ms / 1000U; + uint32_t avg_s = app->avg_interval_ms / 1000U; + if(lst_s > 999U) lst_s = 999U; + if(avg_s > 999U) avg_s = 999U; + + const char* hint = ""; + if(app->last_burst_samples <= 2U) { + hint = " ~SCM"; // brief: consistent with meter packet + } else if(app->last_burst_samples >= 4U) { + hint = " INT?"; // sustained: likely interference + } + + snprintf(s2, sizeof(s2), "I:%3lus A:%3lus%s", + (unsigned long)lst_s, + (unsigned long)avg_s, + hint); + } + canvas_draw_str(canvas, 0, (int32_t)SM_STATS2_Y, s2); + + furi_mutex_release(app->mutex); +} + +// ============================================================ +// Input callback (GUI thread) +// ============================================================ +static bool sm_monitor_input_cb(InputEvent* event, void* context) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + + if(event->type == InputTypeShort) { + switch(event->key) { + case InputKeyOk: + // Skip toggle if a long press was just consumed for reset. + if(app->ok_long_consumed) { + app->ok_long_consumed = false; + return true; + } + view_dispatcher_send_custom_event( + app->view_dispatcher, SmEventMonitorToggle); + return true; + case InputKeyUp: + view_dispatcher_send_custom_event( + app->view_dispatcher, SmEventMonitorThreshUp); + return true; + case InputKeyDown: + view_dispatcher_send_custom_event( + app->view_dispatcher, SmEventMonitorThreshDown); + return true; + case InputKeyLeft: + view_dispatcher_send_custom_event( + app->view_dispatcher, SmEventMonitorFreqPrev); + return true; + case InputKeyRight: + view_dispatcher_send_custom_event( + app->view_dispatcher, SmEventMonitorFreqNext); + return true; + default: + break; + } + } else if(event->type == InputTypeLong) { + switch(event->key) { + case InputKeyOk: + app->ok_long_consumed = true; + view_dispatcher_send_custom_event( + app->view_dispatcher, SmEventMonitorReset); + return true; + case InputKeyUp: + view_dispatcher_send_custom_event( + app->view_dispatcher, SmEventMonitorRateUp); + return true; + case InputKeyDown: + view_dispatcher_send_custom_event( + app->view_dispatcher, SmEventMonitorRateDown); + return true; + default: + break; + } + } + + return false; +} + +// ============================================================ +// View setup (called once from sm_alloc) +// ============================================================ +void scene_monitor_setup_view(SmartMeterApp* app) { + furi_check(app != NULL); + + app->view_monitor = view_alloc(); + furi_check(app->view_monitor != NULL); + + view_set_draw_callback(app->view_monitor, sm_monitor_draw_cb); + view_set_input_callback(app->view_monitor, sm_monitor_input_cb); + view_set_context(app->view_monitor, app); + + view_allocate_model( + app->view_monitor, ViewModelTypeLockFree, sizeof(SmMonitorModel)); + + SmMonitorModel* m = view_get_model(app->view_monitor); + if(m != NULL) m->app = app; + view_commit_model(app->view_monitor, false); + + // Allocate the sample timer here so the callback symbol stays local. + app->sample_timer = furi_timer_alloc(sm_sample_timer_cb, FuriTimerTypePeriodic, app); + furi_check(app->sample_timer != NULL); +} + +// ============================================================ +// Scene handlers +// ============================================================ +void scene_monitor_on_enter(void* context) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + + sm_reset_stats(app); + sm_radio_start(app); + furi_timer_start(app->sample_timer, k_sample_rates[app->rate_index]); + + view_dispatcher_switch_to_view(app->view_dispatcher, SmViewMonitor); +} + +bool scene_monitor_on_event(void* context, SceneManagerEvent event) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + + if(event.type != SceneManagerEventTypeCustom) return false; + + switch(event.event) { + + case SmEventMonitorToggle: + app->monitoring = !app->monitoring; + return true; + + case SmEventMonitorReset: + sm_reset_stats(app); + return true; + + case SmEventMonitorThreshUp: + if(app->threshold_dbm < (int8_t)SM_THRESHOLD_MAX) { + app->threshold_dbm += (int8_t)SM_THRESHOLD_STEP; + } + return true; + + case SmEventMonitorThreshDown: + if(app->threshold_dbm > (int8_t)SM_THRESHOLD_MIN) { + app->threshold_dbm -= (int8_t)SM_THRESHOLD_STEP; + } + return true; + + case SmEventMonitorFreqNext: + app->freq_index = (uint8_t)((app->freq_index + 1U) % SM_FREQ_COUNT); + sm_radio_restart(app); + sm_reset_stats(app); + return true; + + case SmEventMonitorFreqPrev: + app->freq_index = (uint8_t)( + app->freq_index > 0U ? app->freq_index - 1U : SM_FREQ_COUNT - 1U); + sm_radio_restart(app); + sm_reset_stats(app); + return true; + + case SmEventMonitorRateUp: + if(app->rate_index < SM_RATE_COUNT - 1U) { + app->rate_index++; + furi_timer_stop(app->sample_timer); + furi_timer_start(app->sample_timer, k_sample_rates[app->rate_index]); + } + return true; + + case SmEventMonitorRateDown: + if(app->rate_index > 0U) { + app->rate_index--; + furi_timer_stop(app->sample_timer); + furi_timer_start(app->sample_timer, k_sample_rates[app->rate_index]); + } + return true; + + default: + break; + } + return false; +} + +void scene_monitor_on_exit(void* context) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + furi_timer_stop(app->sample_timer); + sm_radio_stop(app); +} \ No newline at end of file diff --git a/Applications/UberGuidoZ/smart_meter_monitor/Source/scene_splash.c b/Applications/UberGuidoZ/smart_meter_monitor/Source/scene_splash.c new file mode 100644 index 000000000..cbd9389a6 --- /dev/null +++ b/Applications/UberGuidoZ/smart_meter_monitor/Source/scene_splash.c @@ -0,0 +1,114 @@ +// #################################################### +// # scene_splash.c - title / splash screen # +// # By: UberGuidoZ | https://github.com/UberGuidoZ/ # +// # v1.11 # +// #################################################### + +#include "smart_meter.h" + +// ============================================================ +// Splash timer callback +// ============================================================ +static void sm_splash_timer_cb(void* context) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + view_dispatcher_send_custom_event(app->view_dispatcher, SM_SPLASH_TIMER_DONE); +} + +// ============================================================ +// Draw callback +// ============================================================ +static void sm_splash_draw_cb(Canvas* canvas, void* model) { + UNUSED(model); + canvas_clear(canvas); + + // ---------------------------------------------------------- + // Box around title block only. Measured at runtime for exact + // centering regardless of font metrics. + // ---------------------------------------------------------- + canvas_set_font(canvas, FontPrimary); + size_t w1 = canvas_string_width(canvas, "Smart Meter"); + size_t w2 = canvas_string_width(canvas, "Monitor"); + size_t box_w = (w1 > w2 ? w1 : w2) + 16U; + int32_t box_x = (int32_t)((128U - box_w) / 2U); + if(box_x < 1) box_x = 1; + canvas_draw_frame(canvas, box_x, 8, (uint8_t)box_w, 32); + canvas_draw_str(canvas, (int32_t)((128U - w1) / 2U), 22, "Smart Meter"); + canvas_draw_str(canvas, (int32_t)((128U - w2) / 2U), 34, "Monitor"); + + // ---------------------------------------------------------- + // Separator and byline (FontKeyboard ~6px/char). + // "v1.11 by UberGuidoZ" = 19 chars * 6 = 114px, x=(128-114)/2 = 7 + // "github.com/UberGuidoZ" = 21 chars * 6 = 126px, x=1 + // ---------------------------------------------------------- + canvas_draw_line(canvas, 10, 44, 118, 44); + canvas_set_font(canvas, FontKeyboard); + canvas_draw_str(canvas, 7, 54, "v1.11 by UberGuidoZ"); + canvas_draw_str(canvas, 1, 62, "github.com/UberGuidoZ"); +} + +// ============================================================ +// Input callback - any key skips the splash +// ============================================================ +static bool sm_splash_input_cb(InputEvent* event, void* context) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + if(event->type == InputTypeShort || event->type == InputTypeLong) { + view_dispatcher_send_custom_event(app->view_dispatcher, SM_SPLASH_TIMER_DONE); + return true; + } + return false; +} + +// ============================================================ +// View setup +// ============================================================ +void scene_splash_setup_view(SmartMeterApp* app) { + furi_check(app != NULL); + app->view_splash = view_alloc(); + furi_check(app->view_splash != NULL); + view_set_draw_callback(app->view_splash, sm_splash_draw_cb); + view_set_input_callback(app->view_splash, sm_splash_input_cb); + view_set_context(app->view_splash, app); +} + +// ============================================================ +// Scene handlers +// ============================================================ +void scene_splash_on_enter(void* context) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + + app->splash_timer = furi_timer_alloc(sm_splash_timer_cb, FuriTimerTypeOnce, app); + furi_check(app->splash_timer != NULL); + furi_timer_start(app->splash_timer, SM_SPLASH_DELAY_MS); + + view_dispatcher_switch_to_view(app->view_dispatcher, SmViewSplash); +} + +bool scene_splash_on_event(void* context, SceneManagerEvent event) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + + if(event.type == SceneManagerEventTypeCustom && + event.event == SM_SPLASH_TIMER_DONE) { + if(app->splash_timer != NULL) { + furi_timer_stop(app->splash_timer); + furi_timer_free(app->splash_timer); + app->splash_timer = NULL; + } + scene_manager_next_scene(app->scene_manager, SmSceneMenu); + return true; + } + return false; +} + +void scene_splash_on_exit(void* context) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + if(app->splash_timer != NULL) { + furi_timer_stop(app->splash_timer); + furi_timer_free(app->splash_timer); + app->splash_timer = NULL; + } +} \ No newline at end of file diff --git a/Applications/UberGuidoZ/smart_meter_monitor/Source/smart_meter.h b/Applications/UberGuidoZ/smart_meter_monitor/Source/smart_meter.h new file mode 100644 index 000000000..a9765985e --- /dev/null +++ b/Applications/UberGuidoZ/smart_meter_monitor/Source/smart_meter.h @@ -0,0 +1,251 @@ +#pragma once + +// ######################################################### +// # smart_meter.h - Smart Meter Monitor for Flipper Zero # +// # By: UberGuidoZ | https://github.com/UberGuidoZ/ # +// # v1.11 # +// ######################################################### + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// ------------------------------------------------------------ +// Screen geometry (actual OLED pixels) +// ------------------------------------------------------------ +#define SM_SCREEN_W 128U +#define SM_SCREEN_H 64U +#define SM_HEADER_H 9U // 8px text row + 1px separator + +// Waveform area +#define SM_WAVE_W 128U // full screen width = sample history +#define SM_WAVE_H 28U // 28px tall +#define SM_WAVE_Y_TOP 10U // top pixel of waveform +#define SM_WAVE_Y_BOT 37U // bottom pixel (SM_WAVE_Y_TOP + SM_WAVE_H - 1) + +// Stats rows (FontKeyboard, 8px tall, 2px gap above each) +#define SM_STATS1_Y 48U // baseline; glyph occupies y=40..48 +#define SM_STATS2_Y 59U // baseline; glyph occupies y=51..59 + +// RSSI scaling +#define SM_RSSI_MIN_DBM (-120) // mapped to 0px waveform height +#define SM_RSSI_MAX_DBM (-40) // mapped to SM_WAVE_H px height +#define SM_RSSI_RANGE 80 // SM_RSSI_MAX_DBM - SM_RSSI_MIN_DBM + +// History buffer = waveform width (1 sample per pixel column) +#define SM_HISTORY_SIZE SM_WAVE_W + +// ------------------------------------------------------------ +// Threshold +// ------------------------------------------------------------ +#define SM_THRESHOLD_DEFAULT (-90) +#define SM_THRESHOLD_MIN (-110) +#define SM_THRESHOLD_MAX (-60) +#define SM_THRESHOLD_STEP 5 + +// ------------------------------------------------------------ +// Burst detection hysteresis +// ------------------------------------------------------------ +#define SM_BURST_ENTER_SAMPLES 2U // consecutive above-threshold to enter burst +#define SM_BURST_EXIT_SAMPLES 3U // consecutive below-threshold to exit burst + +// ------------------------------------------------------------ +// Sample rates +// ------------------------------------------------------------ +#define SM_RATE_COUNT 5U +#define SM_DEFAULT_RATE 2U // 500 ms + +// ------------------------------------------------------------ +// Frequencies +// ------------------------------------------------------------ +#define SM_FREQ_COUNT 3U +#define SM_DEFAULT_FREQ 2U // 915 MHz (most common in US) + +// ------------------------------------------------------------ +// Splash +// ------------------------------------------------------------ +#define SM_SPLASH_DELAY_MS 3000U +#define SM_SPLASH_TIMER_DONE 0U + +// ------------------------------------------------------------ +// Instructions +// ------------------------------------------------------------ +#define SM_INSTR_PAGES 4U +#define SM_INSTR_BODY_LINES 5U + +// ------------------------------------------------------------ +// Scene IDs +// Order must match sm_on_enter/event/exit arrays in smart_meter_app.c. +// ------------------------------------------------------------ +typedef enum { + SmSceneSplash = 0, + SmSceneMenu, + SmSceneFreq, + SmSceneMonitor, + SmSceneInstructions, + SmSceneAbout, + SmSceneCount, +} SmSceneId; + +// ------------------------------------------------------------ +// View IDs +// ------------------------------------------------------------ +typedef enum { + SmViewSplash = 0, + SmViewMenu, + SmViewFreq, + SmViewMonitor, + SmViewInstructions, + SmViewAbout, + SmViewCount, +} SmViewId; + +// ------------------------------------------------------------ +// Custom events +// ------------------------------------------------------------ +typedef enum { + SmEventMenuStart = 0, + SmEventMenuFreq, + SmEventMenuInstr, + SmEventMenuAbout, +} SmMenuEvent; + +typedef enum { + SmEventMonitorToggle = 0, + SmEventMonitorReset, + SmEventMonitorRateUp, + SmEventMonitorRateDown, + SmEventMonitorFreqNext, + SmEventMonitorFreqPrev, + SmEventMonitorThreshUp, + SmEventMonitorThreshDown, +} SmMonitorEvent; + +typedef enum { + SmEventInstrPageLeft = 0, + SmEventInstrPageRight, +} SmInstrEvent; + +// ------------------------------------------------------------ +// Forward declaration - allows model structs below to hold +// a SmartMeterApp* before the full struct is defined. +// ------------------------------------------------------------ +typedef struct SmartMeterApp_s SmartMeterApp; + +// ------------------------------------------------------------ +// View models +// ------------------------------------------------------------ + +// Monitor view: draw callback reads app state through this pointer. +typedef struct { + SmartMeterApp* app; +} SmMonitorModel; + +// Instructions view: tracks which page is displayed. +typedef struct { + uint8_t page; +} SmInstrModel; + +// ------------------------------------------------------------ +// Full application state struct +// ------------------------------------------------------------ +struct SmartMeterApp_s { + // Scene / view management + SceneManager* scene_manager; + ViewDispatcher* view_dispatcher; + Submenu* submenu_menu; + Submenu* submenu_freq; + View* view_splash; + View* view_monitor; + View* view_instructions; + View* view_about; + + // Timers and sync + FuriTimer* sample_timer; + FuriTimer* splash_timer; + FuriMutex* mutex; + + // Radio state + bool radio_running; + bool monitoring; // capture on / off + bool ok_long_consumed; // prevents long-press release from also triggering short + uint8_t freq_index; // index into k_frequencies[] + uint8_t rate_index; // index into k_sample_rates[] + + // RSSI circular buffer + int8_t rssi_history[SM_HISTORY_SIZE]; + uint32_t rssi_head; // next write position + uint32_t rssi_count; // samples stored (capped at SM_HISTORY_SIZE) + int8_t rssi_current; // most recent RSSI value + + // Threshold (dBm, adjusted by UP/DOWN) + int8_t threshold_dbm; + + // Burst detection + bool in_burst; + uint8_t burst_above; // consecutive above-threshold samples + uint8_t burst_below; // consecutive below-threshold samples + + // Transmission stats + uint32_t burst_count; // total detected transmissions + uint32_t last_burst_tick; // furi_get_tick() of last burst exit + uint32_t last_interval_ms; // interval between last two bursts + uint32_t avg_interval_ms; // running average interval + + // Tx/minute - rolling 60s window of burst exit timestamps + uint32_t burst_timestamps[30U]; // circular buffer of burst ticks + uint8_t burst_ts_head; // next write index + uint8_t burst_ts_count; // entries stored (capped at 30) + uint8_t tx_per_min; // transmissions in last 60s + + // Burst duration / SCM framing hint + uint32_t burst_enter_tick; // tick when burst was entered + uint8_t burst_sample_count; // above-threshold samples in current burst + uint8_t last_burst_samples; // sample count of last completed burst + + // Notifications + NotificationApp* notifications; +}; + +// ------------------------------------------------------------ +// Scene handler declarations +// ------------------------------------------------------------ +void scene_splash_on_enter(void* context); +bool scene_splash_on_event(void* context, SceneManagerEvent event); +void scene_splash_on_exit(void* context); + +void scene_menu_on_enter(void* context); +bool scene_menu_on_event(void* context, SceneManagerEvent event); +void scene_menu_on_exit(void* context); + +void scene_freq_on_enter(void* context); +bool scene_freq_on_event(void* context, SceneManagerEvent event); +void scene_freq_on_exit(void* context); + +void scene_monitor_on_enter(void* context); +bool scene_monitor_on_event(void* context, SceneManagerEvent event); +void scene_monitor_on_exit(void* context); + +void scene_instructions_on_enter(void* context); +bool scene_instructions_on_event(void* context, SceneManagerEvent event); +void scene_instructions_on_exit(void* context); + +void scene_about_on_enter(void* context); +bool scene_about_on_event(void* context, SceneManagerEvent event); +void scene_about_on_exit(void* context); + +// ------------------------------------------------------------ +// View setup functions (called once from sm_alloc) +// ------------------------------------------------------------ +void scene_splash_setup_view(SmartMeterApp* app); +void scene_monitor_setup_view(SmartMeterApp* app); +void scene_instructions_setup_view(SmartMeterApp* app); +void scene_about_setup_view(SmartMeterApp* app); \ No newline at end of file diff --git a/Applications/UberGuidoZ/smart_meter_monitor/Source/smart_meter_app.c b/Applications/UberGuidoZ/smart_meter_monitor/Source/smart_meter_app.c new file mode 100644 index 000000000..cd7b86662 --- /dev/null +++ b/Applications/UberGuidoZ/smart_meter_monitor/Source/smart_meter_app.c @@ -0,0 +1,189 @@ +// ###################################################### +// # smart_meter_app.c - entry point and app lifecycle # +// # By: UberGuidoZ | https://github.com/UberGuidoZ/ # +// # v1.11 # +// ###################################################### + +#include "smart_meter.h" +#include + +// ============================================================ +// Scene handler tables +// Order must match SmSceneId enum in smart_meter.h. +// ============================================================ +static void (*const sm_on_enter_handlers[])(void*) = { + scene_splash_on_enter, + scene_menu_on_enter, + scene_freq_on_enter, + scene_monitor_on_enter, + scene_instructions_on_enter, + scene_about_on_enter, +}; + +static bool (*const sm_on_event_handlers[])(void*, SceneManagerEvent) = { + scene_splash_on_event, + scene_menu_on_event, + scene_freq_on_event, + scene_monitor_on_event, + scene_instructions_on_event, + scene_about_on_event, +}; + +static void (*const sm_on_exit_handlers[])(void*) = { + scene_splash_on_exit, + scene_menu_on_exit, + scene_freq_on_exit, + scene_monitor_on_exit, + scene_instructions_on_exit, + scene_about_on_exit, +}; + +static const SceneManagerHandlers sm_scene_manager_handlers = { + .on_enter_handlers = sm_on_enter_handlers, + .on_event_handlers = sm_on_event_handlers, + .on_exit_handlers = sm_on_exit_handlers, + .scene_num = SmSceneCount, +}; + +// ============================================================ +// ViewDispatcher callbacks +// ============================================================ + +static bool sm_custom_event_cb(void* context, uint32_t event) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + return scene_manager_handle_custom_event(app->scene_manager, event); +} + +static bool sm_back_event_cb(void* context) { + SmartMeterApp* app = (SmartMeterApp*)context; + furi_check(app != NULL); + return scene_manager_handle_back_event(app->scene_manager); +} + +// ============================================================ +// Allocate all app resources +// ============================================================ +static SmartMeterApp* sm_alloc(void) { + SmartMeterApp* app = malloc(sizeof(SmartMeterApp)); + furi_check(app != NULL); + memset(app, 0, sizeof(SmartMeterApp)); + + app->freq_index = SM_DEFAULT_FREQ; + app->rate_index = SM_DEFAULT_RATE; + app->threshold_dbm = (int8_t)SM_THRESHOLD_DEFAULT; + app->monitoring = true; + + // ---------------------------------------------------------- + // Scene manager + // ---------------------------------------------------------- + app->scene_manager = scene_manager_alloc(&sm_scene_manager_handlers, app); + furi_check(app->scene_manager != NULL); + + // ---------------------------------------------------------- + // View dispatcher + // ---------------------------------------------------------- + app->view_dispatcher = view_dispatcher_alloc(); + furi_check(app->view_dispatcher != NULL); + + view_dispatcher_set_event_callback_context(app->view_dispatcher, app); + view_dispatcher_set_custom_event_callback(app->view_dispatcher, sm_custom_event_cb); + view_dispatcher_set_navigation_event_callback(app->view_dispatcher, sm_back_event_cb); + + Gui* gui = furi_record_open(RECORD_GUI); + view_dispatcher_attach_to_gui(app->view_dispatcher, gui, ViewDispatcherTypeFullscreen); + furi_record_close(RECORD_GUI); + + // ---------------------------------------------------------- + // Submenus + // ---------------------------------------------------------- + app->submenu_menu = submenu_alloc(); + furi_check(app->submenu_menu != NULL); + view_dispatcher_add_view( + app->view_dispatcher, SmViewMenu, submenu_get_view(app->submenu_menu)); + + app->submenu_freq = submenu_alloc(); + furi_check(app->submenu_freq != NULL); + view_dispatcher_add_view( + app->view_dispatcher, SmViewFreq, submenu_get_view(app->submenu_freq)); + + // ---------------------------------------------------------- + // Custom views + // ---------------------------------------------------------- + scene_splash_setup_view(app); + view_dispatcher_add_view(app->view_dispatcher, SmViewSplash, app->view_splash); + + scene_monitor_setup_view(app); + view_dispatcher_add_view(app->view_dispatcher, SmViewMonitor, app->view_monitor); + + scene_instructions_setup_view(app); + view_dispatcher_add_view(app->view_dispatcher, SmViewInstructions, app->view_instructions); + + scene_about_setup_view(app); + view_dispatcher_add_view(app->view_dispatcher, SmViewAbout, app->view_about); + + // ---------------------------------------------------------- + // Mutex and notifications + // ---------------------------------------------------------- + app->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + furi_check(app->mutex != NULL); + + app->notifications = furi_record_open(RECORD_NOTIFICATION); + furi_check(app->notifications != NULL); + + return app; +} + +// ============================================================ +// Free all resources in reverse-allocation order +// ============================================================ +static void sm_free(SmartMeterApp* app) { + furi_check(app != NULL); + + if(app->sample_timer != NULL) { + furi_timer_stop(app->sample_timer); + furi_timer_free(app->sample_timer); + app->sample_timer = NULL; + } + + if(app->splash_timer != NULL) { + furi_timer_stop(app->splash_timer); + furi_timer_free(app->splash_timer); + app->splash_timer = NULL; + } + + furi_record_close(RECORD_NOTIFICATION); + furi_mutex_free(app->mutex); + + view_dispatcher_remove_view(app->view_dispatcher, SmViewAbout); + view_dispatcher_remove_view(app->view_dispatcher, SmViewInstructions); + view_dispatcher_remove_view(app->view_dispatcher, SmViewMonitor); + view_dispatcher_remove_view(app->view_dispatcher, SmViewSplash); + view_dispatcher_remove_view(app->view_dispatcher, SmViewFreq); + view_dispatcher_remove_view(app->view_dispatcher, SmViewMenu); + + view_free(app->view_about); + view_free(app->view_instructions); + view_free(app->view_monitor); + view_free(app->view_splash); + submenu_free(app->submenu_freq); + submenu_free(app->submenu_menu); + + view_dispatcher_free(app->view_dispatcher); + scene_manager_free(app->scene_manager); + + free(app); +} + +// ============================================================ +// Application entry point +// ============================================================ +int32_t smart_meter_app(void* p) { + UNUSED(p); + + SmartMeterApp* app = sm_alloc(); + scene_manager_next_scene(app->scene_manager, SmSceneSplash); + view_dispatcher_run(app->view_dispatcher); + sm_free(app); + return 0; +} \ No newline at end of file diff --git a/Applications/UberGuidoZ/smart_meter_monitor/Source/smart_meter_icon.png b/Applications/UberGuidoZ/smart_meter_monitor/Source/smart_meter_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ebda0acf14c24fbfb0ea95b36458767577a573f5 GIT binary patch literal 371 zcmV-(0gV2MP)4Tx0C=2ZU|>AK00b-s3=Fw>B}GB*P6`o`Q3~u27#M*N3=)%z3m9+%psH`z zAZjn`;!%eSaHgadCj;pZK&(;(v>a%p2#_t34q