Procházet zdrojové kódy

Add '.modules/geiger/' from commit 'dbe071d92cb733ea203af4a3e31c4206e1260b3e'

git-subtree-dir: .modules/geiger
git-subtree-mainline: 20a507bf19a3c64f588772ce5cd791ff3a7b3bea
git-subtree-split: dbe071d92cb733ea203af4a3e31c4206e1260b3e
Willy-JL před 2 roky
rodič
revize
2094f33dea
31 změnil soubory, kde provedl 610 přidání a 0 odebrání
  1. 224 0
      .modules/geiger/README.md
  2. binární
      .modules/geiger/flipper_geiger.fap
  3. 13 0
      .modules/geiger/flipper_geiger/application.fam
  4. 373 0
      .modules/geiger/flipper_geiger/flipper_geiger.c
  5. binární
      .modules/geiger/flipper_geiger/geiger.png
  6. binární
      .modules/geiger/img/dice.jpg
  7. binární
      .modules/geiger/img/flipper1.png
  8. binární
      .modules/geiger/img/flipper10.png
  9. binární
      .modules/geiger/img/flipper11.png
  10. binární
      .modules/geiger/img/flipper12.png
  11. binární
      .modules/geiger/img/flipper13.png
  12. binární
      .modules/geiger/img/flipper14.png
  13. binární
      .modules/geiger/img/flipper2.png
  14. binární
      .modules/geiger/img/flipper3.png
  15. binární
      .modules/geiger/img/flipper4.png
  16. binární
      .modules/geiger/img/flipper5.png
  17. binární
      .modules/geiger/img/flipper6.png
  18. binární
      .modules/geiger/img/flipper7.png
  19. binární
      .modules/geiger/img/flipper8.png
  20. binární
      .modules/geiger/img/flipper9.png
  21. binární
      .modules/geiger/img/jack.png
  22. binární
      .modules/geiger/img/logo.jpg
  23. binární
      .modules/geiger/img/schema.jpg
  24. binární
      .modules/geiger/img/user/IMG-1552.jpg
  25. binární
      .modules/geiger/img/user/abQXuEz.jpg
  26. binární
      .modules/geiger/img/user/testing-of-the-geiger-counter-v0-3zv9gdq4nt0b1.jpg
  27. binární
      .modules/geiger/img/version.png
  28. binární
      .modules/geiger/img/zoom0.png
  29. binární
      .modules/geiger/img/zoom1.png
  30. binární
      .modules/geiger/img/zoom2.png
  31. binární
      .modules/geiger/img/zoom3.png

+ 224 - 0
.modules/geiger/README.md

@@ -0,0 +1,224 @@
+# flipperzero-geigercounter
+☢☢ A geiger counter application for the Flipper Zero ☢☢
+
+![banner](https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/logo.jpg)
+(banner has been made with **Midjourney**)
+
+
+You need a **geiger counter** board to run this application, this board can be used: https://aliexpress.com/item/1005004074447209.html
+
+You also need jumper wires to connect the board on the **Flipper Zero**.
+
+**Note 1:** This board uses a **J305** geiger tube. According this [website](https://www.rhelectronics.store/j305-glassy-geiger-muller-tube-nuclear-radiation-sensor) gamma conversion factor is **0.0081** for this tube. This value has been declared in the header of the source file so you can change it easily if needed. Incorrect conversion factor will give false measurements when **Sv** or **Rad** is selected.
+
+**Note 2:** **J305** geiger tube is only sensible to **beta** and **gamma** rays. **Alpha** rays cannot be detected. 
+
+**Usable** radioactive sources: 
+- natural uranium (alpha, beta, gamma)
+- natural thorium (alpha, beta, gamma)
+- radium-226 (alpha, beta, gamma)
+- cobalt-60 (beta & gamma)
+- iodine-131 (beta & gamma)
+
+**Not really usable** radioactive sources (must be in contact with the geiger tube to be detected): 
+- americium-241 (alpha & low gamma, some strong beta/gamma rays are emitted during radioactive cascade or due to the presence of radioisotope impurities)
+- high purity metallic uranium/thorium (same as am241)
+
+
+**Totaly unusable** radioactive sources: 
+- polonium-210 (pure alpha)
+- tritium (very low beta)
+
+
+The geiger counter board can be powered with +5V power pin of the **Flipper Zero**. This pin will automatically be enabled when the program is launched. 
+
+Output pin for measure on arduino cannot be used on the **Flipper Zero** because output voltage is too low. You can use jack out port instead. Just cut audio jack cable and connect audio channel (left, right or both together) with a cut half male jumper wire to **A7** GPIO:
+
+<p align="center"><img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/jack.png" width=40% height=40%></p>
+
+Black wire is usually used for the ground (sleeve on the schematic). You can use a multimeter to be sure or simply test other wires.
+
+Global schema:
+
+<p align="center"><img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/schema.jpg" width=75% height=75%></p>
+
+UI of the application:
+
+<p align="center"><img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/flipper3.png" width=33% height=33%></p>
+
+**CPS**: **c**ounts **p**er **s**econd (instantaneous measure of the radioactivity). CPS is alway displayed on the left corner.
+
+**CPM**: **c**ounts **p**er **m**inute (the sum of CPS over a period of one minute). Other units of measurement can be chosen with **Left/Right**.
+
+New CPS bar measure appears on the left every second.
+
+## Build the program
+
+Assuming the toolchain is already installed, copy **flipper_geiger** directory to **applications_user**
+
+Plug your **Flipper Zero** and build the geiger counter:
+```
+./fbt launch_app APPSRC=applications_user/flipper_geiger
+```
+
+The program will automatically be launched after compilation
+
+<img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/flipper1.png" width=25% height=25%>
+
+**A4** GPIO can be connected on **A7** GPIO to test this application without using a geiger tube. **A4** GPIO is generating a signal whose frequency changes every second.
+
+**Button assignments**: 
+
+button  | function
+------------- | -------------
+**Ok** *[long press]*  | Clear the graph
+**Left/Right** *[short press]* | Choose unit on the right corner (cpm, μSv/h, mSv/y, Rad/h, mRad/h, uRad/h), **cps** on the left is always displayed
+**Up** *[long press]*  | Enable/disable recording, led of **Flipper Zero** is colored in red when recording
+**Up/Down** *[short press]*  | Zoom/unzoom 
+**Down** *[long press]*  | Display version of the application
+**Back** *[long press]*  | Exit
+
+If you don't want to build this application, just simply copy **flipper_geiger.fap** on your **Flipper Zero** 
+
+Build has been made with official toolchain, **API Mismatch** error may appear if you are using custom firmware. You can bypass this error but the program may crash.
+
+## Use cases
+
+Ambient radioactivity (descendants of radon gas are detected, not radon itself):
+
+<img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/flipper2.png" width=25% height=25%> <img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/flipper8.png" width=25% height=25%> <img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/flipper9.png" width=25% height=25%>
+
+Measure of uranium ore piece inside a lead container:
+
+<img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/flipper3.png" width=25% height=25%> <img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/flipper12.png" width=25% height=25%> <img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/flipper13.png" width=25% height=25%> <img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/flipper14.png" width=25% height=25%>
+
+**Note:** measures in **Sv** or **Rad** are not precise
+
+Measure of uranium ore piece (the most radioactive part):
+
+<img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/flipper4.png" width=25% height=25%>
+
+Measure of radium dial pointers:
+
+<img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/flipper5.png" width=25% height=25%>
+
+All previous measures in a row (the scale of the graph is automatically adjusted):
+
+<img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/flipper6.png" width=25% height=25%>
+
+Measure of uranium orange pottery:
+
+<img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/flipper10.png" width=25% height=25%>
+
+Measure of americium-241 button from a smoke detector (descendants of americium or radioisotope impurities are detected, not americium itself):
+
+<img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/flipper11.png" width=25% height=25%>
+
+**A4** GPIO on **A7** GPIO (to test this program without a geiger tube):
+
+<img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/flipper7.png" width=25% height=25%>
+
+Zoom levels (the third picture is the default zoom):
+
+<img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/zoom0.png" width=25% height=25%> <img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/zoom1.png" width=25% height=25%> <img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/zoom2.png" width=25% height=25%> <img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/zoom3.png" width=25% height=25%>
+
+Version of the application (press down button during 1 sec to display version):
+
+<img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/version.png" width=25% height=25%>
+
+## Recording function
+
+Output **CSV** files are stored at the root of SD card. Date & time are set in the name file (example: **geiger-2023-07-03--23-48-15.csv**)
+
+Data sample:
+
+epoch  | cps
+------------- | -------------
+0  | 10
+1  | 14
+2  | 8
+3  | 11
+4  | 9
+
+## Atomic Dice Roller
+
+<p align="center"><img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/dice.jpg" width=25% height=25%></p>
+
+I maintain another application that uses the **geiger board** to roll dice by using radioactivity: https://github.com/nmrr/flipperzero-atomicdiceroller
+
+## Gallery/video of the community
+
+[BRD8 [Reddit]](https://www.reddit.com/user/BRD8/) - https://www.reddit.com/r/flipperzero/comments/110062z/am_i_a_hacker_now_mom/: 
+
+<img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/user/abQXuEz.jpg" width=50% height=50%>
+
+[Funbob235 [Reddit]](https://www.reddit.com/user/Funbob235/) - https://www.reddit.com/r/flipperzero/comments/13m1qly/testing_of_the_geiger_counter/: 
+
+<img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/user/testing-of-the-geiger-counter-v0-3zv9gdq4nt0b1.jpg" width=50% height=50%>
+
+[Axewarior [Reddit]](https://www.reddit.com/user/axewarior/) - https://www.reddit.com/r/flipperzero/comments/14krjs2/gieger_counter/
+
+<img src="https://github.com/nmrr/flipperzero-geigercounter/blob/main/img/user/IMG-1552.jpg" width=50% height=50%>
+
+[Seanclark2409 [YouTube]](https://www.youtube.com/@seanclark2409) (click on the picture to see the video): 
+
+[![Watch the video](https://img.youtube.com/vi/JQB2jvY1oZ0/maxresdefault.jpg)](https://youtu.be/JQB2jvY1oZ0)
+
+[Boboso5676 [YouTube]](https://www.youtube.com/@boboso5676) (click on the picture to see the video):
+
+[![Watch the video](https://img.youtube.com/vi/jYQlC2NJScQ/maxresdefault.jpg)](https://youtu.be/jYQlC2NJScQ)
+
+[Talking Sasquach [YouTube]](https://www.youtube.com/@TalkingSasquach) (click on the picture to see the video):
+
+[![Watch the video](https://img.youtube.com/vi/-I57_S_AXYY/maxresdefault.jpg)](https://youtu.be/-I57_S_AXYY)
+
+## What's next ?
+
+Here are some nice ideas to improve this app:
+
+* ~~Save output data in XML / JSON file~~ **DONE !** Output data are stored in CSV (lighter than XML / JSON and easier to parse)
+* ~~Use the geiger board as random number generator~~ **DONE !** A separate project uses the same geiger board to roll dice: https://github.com/nmrr/flipperzero-atomicdiceroller
+* Send data on the air in real time to monitor remotly
+* Buzz when it gets dangerous like a dosimeter
+
+## Changelog
+
+* 2023-08-06
+  * Code optimization (shift operation on CPS array has been removed)
+  * Version section has been added
+
+* 2023-07-03
+  * Data recording function has been added
+
+* 2023-06-25
+  * Add zoom capability
+  * User gallery has been added
+
+* 2023-06-08
+  * Bug fix
+
+* 2023-04-11
+  * More usable/unusable sources
+  * Rad unit has been added
+  * Code refactoring by replacing old mutex call by new method
+
+* 2023-03-01
+  * Usable/unusable sources have been added
+
+* 2023-02-26
+  * More clarity about how to connect audio jack cable on A7 GPIO
+
+* 2023-02-02
+  * μSv/h and mSv/y have been added
+  * 5V pin is now automatically enabled when the program is launched
+
+* 2023-01-15
+  * Code fix & optimizations
+  * More events can be handled without any issue
+
+* 2023-01-09
+  * Code fix
+  * Global schema has been added
+
+* 2023-01-08
+  * Initial release

binární
.modules/geiger/flipper_geiger.fap


+ 13 - 0
.modules/geiger/flipper_geiger/application.fam

@@ -0,0 +1,13 @@
+App(
+    appid="flipper_geiger",
+    name="Geiger Counter",
+    apptype=FlipperAppType.EXTERNAL,
+    entry_point="flipper_geiger_app",
+    cdefines=["APP_GEIGER"],
+    requires=[
+        "gui",
+    ],
+    stack_size=2 * 1024,
+    fap_icon="geiger.png",
+    fap_category="Tools",
+)

+ 373 - 0
.modules/geiger/flipper_geiger/flipper_geiger.c

@@ -0,0 +1,373 @@
+// CC0 1.0 Universal (CC0 1.0)
+// Public Domain Dedication
+// https://github.com/nmrr
+
+#include <stdio.h>
+#include <furi.h>
+#include <gui/gui.h>
+#include <input/input.h>
+#include <notification/notification_messages.h>
+#include <furi_hal_random.h>
+#include <furi_hal_pwm.h>
+#include <furi_hal_power.h>
+
+#include <storage/storage.h>
+#include <stream/buffered_file_stream.h>
+
+#include <locale/locale.h>
+
+#define SCREEN_SIZE_X 128
+#define SCREEN_SIZE_Y 64
+
+// FOR J305 GEIGER TUBE
+#define CONVERSION_FACTOR 0.0081
+
+typedef enum {
+    EventTypeInput,
+    ClockEventTypeTick,
+    EventGPIO,
+} EventType;
+
+typedef struct {
+    EventType type;
+    InputEvent input;
+} EventApp;
+
+typedef struct {
+    FuriMutex* mutex;
+    uint32_t cps, cpm;
+    uint32_t line[SCREEN_SIZE_X];
+    float coef;
+    uint8_t data;
+    uint8_t zoom;
+    uint8_t newLinePosition;
+    uint8_t version;
+} mutexStruct;
+
+static void draw_callback(Canvas* canvas, void* ctx) 
+{
+    furi_assert(ctx);
+
+    mutexStruct* mutexVal = ctx;
+    mutexStruct mutexDraw;
+    furi_mutex_acquire(mutexVal->mutex, FuriWaitForever);
+    memcpy(&mutexDraw, mutexVal, sizeof(mutexStruct));
+    furi_mutex_release(mutexVal->mutex);
+
+    if (mutexDraw.version == 0)
+    {
+        char buffer[32];
+        if (mutexDraw.data == 0) snprintf(buffer, sizeof(buffer), "%ld cps - %ld cpm", mutexDraw.cps, mutexDraw.cpm);
+        else if (mutexDraw.data == 1) snprintf(buffer, sizeof(buffer), "%ld cps - %.2f uSv/h", mutexDraw.cps, ((double)mutexDraw.cpm*(double)CONVERSION_FACTOR));
+        else if (mutexDraw.data == 2) snprintf(buffer, sizeof(buffer), "%ld cps - %.2f mSv/y", mutexDraw.cps, (((double)mutexDraw.cpm*(double)CONVERSION_FACTOR))*(double)8.76);
+        else if (mutexDraw.data == 3) snprintf(buffer, sizeof(buffer), "%ld cps - %.4f Rad/h", mutexDraw.cps, ((double)mutexDraw.cpm*(double)CONVERSION_FACTOR)/(double)10000);
+        else if (mutexDraw.data == 4) snprintf(buffer, sizeof(buffer), "%ld cps - %.2f mR/h", mutexDraw.cps, ((double)mutexDraw.cpm*(double)CONVERSION_FACTOR)/(double)10);
+        else snprintf(buffer, sizeof(buffer), "%ld cps - %.2f uR/h", mutexDraw.cps, ((double)mutexDraw.cpm*(double)CONVERSION_FACTOR)*(double)100);
+
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignBottom, buffer);
+
+        uint8_t linePosition = mutexDraw.newLinePosition;
+
+        if (mutexDraw.zoom == 0)
+        {
+            for (int i=0;i<SCREEN_SIZE_X;i+=8)
+            {
+                if (linePosition != 0) linePosition--;
+                else linePosition = SCREEN_SIZE_X - 1;
+
+                float Y = SCREEN_SIZE_Y-(mutexDraw.line[linePosition]*mutexDraw.coef);
+                for (int j=0;j<8;j++)canvas_draw_line(canvas, i+j, Y, i+j, SCREEN_SIZE_Y);
+            }
+        }
+        else if (mutexDraw.zoom == 1)
+        {
+            for (int i=0;i<SCREEN_SIZE_X;i+=4)
+            {
+                if (linePosition != 0) linePosition--;
+                else linePosition = SCREEN_SIZE_X - 1;
+
+                float Y = SCREEN_SIZE_Y-(mutexDraw.line[linePosition]*mutexDraw.coef);
+                for (int j=0;j<4;j++)canvas_draw_line(canvas, i+j, Y, i+j, SCREEN_SIZE_Y);
+            }
+        }
+        else if (mutexDraw.zoom == 2)
+        {
+            for (int i=0;i<SCREEN_SIZE_X;i+=2)
+            {
+                if (linePosition != 0) linePosition--;
+                else linePosition = SCREEN_SIZE_X - 1;
+
+                float Y = SCREEN_SIZE_Y-(mutexDraw.line[linePosition]*mutexDraw.coef);
+                for (int j=0;j<2;j++)canvas_draw_line(canvas, i+j, Y, i+j, SCREEN_SIZE_Y);
+            }
+        }
+        else if (mutexDraw.zoom == 3)
+        {
+            for (int i=0;i<SCREEN_SIZE_X;i++)
+            {
+                if (linePosition != 0) linePosition--;
+                else linePosition = SCREEN_SIZE_X - 1;
+
+                float Y = SCREEN_SIZE_Y-(mutexDraw.line[linePosition]*mutexDraw.coef);
+                canvas_draw_line(canvas, i, Y, i, SCREEN_SIZE_Y);
+            }
+        }
+    }
+    else
+    {
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str_aligned(canvas, 64, 10, AlignCenter, AlignBottom, "Geiger Counter");
+        canvas_draw_str_aligned(canvas, 64, 20, AlignCenter, AlignBottom, "Version 20230806");
+        canvas_draw_str_aligned(canvas, 64, 40, AlignCenter, AlignBottom, "github.com/nmrr");
+    }
+}
+
+static void input_callback(InputEvent* input_event, void* ctx) 
+{
+    furi_assert(ctx);
+    FuriMessageQueue* event_queue = ctx;
+    EventApp event = {.type = EventTypeInput, .input = *input_event};
+    furi_message_queue_put(event_queue, &event, FuriWaitForever);
+}
+
+static void clock_tick(void* ctx) {
+    furi_assert(ctx);
+
+    uint32_t randomNumber = furi_hal_random_get();
+    randomNumber &= 0xFFF;
+    if (randomNumber == 0) randomNumber = 1;
+
+    furi_hal_pwm_set_params(FuriHalPwmOutputIdLptim2PA4, randomNumber, 50);
+
+    FuriMessageQueue* queue = ctx;
+    EventApp event = {.type = ClockEventTypeTick};
+    furi_message_queue_put(queue, &event, 0);
+}
+
+static void gpiocallback(void* ctx) {
+    furi_assert(ctx);
+    FuriMessageQueue* queue = ctx;
+    EventApp event = {.type = EventGPIO};
+    furi_message_queue_put(queue, &event, 0);
+}
+
+int32_t flipper_geiger_app() 
+{
+    EventApp event;
+    FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(EventApp));
+
+    furi_hal_gpio_init(&gpio_ext_pa7, GpioModeInterruptFall, GpioPullUp, GpioSpeedVeryHigh);
+    furi_hal_pwm_start(FuriHalPwmOutputIdLptim2PA4, 5, 50);
+
+    mutexStruct mutexVal;
+    mutexVal.cps = 0;
+    mutexVal.cpm = 0;
+    for (int i=0;i<SCREEN_SIZE_X;i++) mutexVal.line[i] = 0;
+    mutexVal.coef = 1;
+    mutexVal.data = 0;
+    mutexVal.zoom = 2;
+    mutexVal.newLinePosition = 0;
+    mutexVal.version = 0;
+
+    uint32_t counter = 0;
+
+    mutexVal.mutex= furi_mutex_alloc(FuriMutexTypeNormal);
+    if(!mutexVal.mutex) {
+        furi_message_queue_free(event_queue);
+        return 255;
+    }
+
+    ViewPort* view_port = view_port_alloc();
+    view_port_draw_callback_set(view_port, draw_callback, &mutexVal.mutex);
+    view_port_input_callback_set(view_port, input_callback, event_queue);
+
+    furi_hal_gpio_add_int_callback(&gpio_ext_pa7, gpiocallback, event_queue);
+
+    Gui* gui = furi_record_open(RECORD_GUI);
+    gui_add_view_port(gui, view_port, GuiLayerFullscreen);
+
+    FuriTimer* timer = furi_timer_alloc(clock_tick, FuriTimerTypePeriodic, event_queue);
+    furi_timer_start(timer, 1000);
+
+    // ENABLE 5V pin
+    furi_hal_power_enable_otg();
+
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    Stream* file_stream = buffered_file_stream_alloc(storage);
+    FuriString* dataString = furi_string_alloc();
+    uint32_t epoch = 0;
+    uint8_t recordData = 0;
+
+    NotificationApp* notification = furi_record_open(RECORD_NOTIFICATION);
+
+    while(1) 
+    {
+        FuriStatus event_status = furi_message_queue_get(event_queue, &event, FuriWaitForever);
+        
+        uint8_t screenRefresh = 0;
+
+        if (event_status == FuriStatusOk)
+        {   
+            if(event.type == EventTypeInput) 
+            {
+                if(event.input.key == InputKeyBack && event.input.type == InputTypeLong) 
+                {
+                    break;
+                }
+                else if(event.input.key == InputKeyOk && event.input.type == InputTypeLong)
+                {
+                    counter = 0;
+                    furi_mutex_acquire(mutexVal.mutex, FuriWaitForever);
+
+                    mutexVal.cps = 0;
+                    mutexVal.cpm = 0;
+                    for (uint8_t i=0;i<SCREEN_SIZE_X;i++) mutexVal.line[i] = 0;
+                    mutexVal.newLinePosition = 0;
+
+                    screenRefresh = 1;
+                    furi_mutex_release(mutexVal.mutex);
+                }
+                else if(event.input.key == InputKeyUp && event.input.type == InputTypeLong)
+                {
+                    if (recordData == 0) 
+                    {
+                        notification_message(notification, &sequence_set_only_red_255);
+
+                        FuriHalRtcDateTime datetime;
+                        furi_hal_rtc_get_datetime(&datetime);
+
+                        char path[64];
+                        snprintf(path, sizeof(path), EXT_PATH("/geiger-%.4d-%.2d-%.2d--%.2d-%.2d-%.2d.csv"), datetime.year, datetime.month, datetime.day, datetime.hour, datetime.minute, datetime.second);
+
+                        buffered_file_stream_open(file_stream, path, FSAM_WRITE, FSOM_CREATE_ALWAYS);
+                        furi_string_printf(dataString, "epoch,cps\n");
+                        stream_write_string(file_stream, dataString);
+                        epoch = 0;
+                        recordData = 1;
+                    }
+                    else
+                    {
+                        buffered_file_stream_close(file_stream);
+                        notification_message(notification, &sequence_reset_red);
+                        recordData = 0;
+                    }
+                }
+                else if((event.input.key == InputKeyLeft && event.input.type == InputTypeShort))
+                {
+                    furi_mutex_acquire(mutexVal.mutex, FuriWaitForever);
+
+                    if (mutexVal.data != 0) mutexVal.data--;
+                    else mutexVal.data = 5;
+
+                    screenRefresh = 1;
+                    furi_mutex_release(mutexVal.mutex);
+                }
+                else if((event.input.key == InputKeyRight && event.input.type == InputTypeShort))
+                {
+                    furi_mutex_acquire(mutexVal.mutex, FuriWaitForever);
+
+                    if (mutexVal.data != 5) mutexVal.data++;
+                    else mutexVal.data = 0;
+
+                    screenRefresh = 1;
+                    furi_mutex_release(mutexVal.mutex);
+                }
+                else if((event.input.key == InputKeyUp && event.input.type == InputTypeShort))
+                {
+                    furi_mutex_acquire(mutexVal.mutex, FuriWaitForever);
+                    if (mutexVal.zoom != 0) mutexVal.zoom--;
+
+                    screenRefresh = 1;
+                    furi_mutex_release(mutexVal.mutex);
+
+                }
+                else if((event.input.key == InputKeyDown && event.input.type == InputTypeShort))
+                {
+                    furi_mutex_acquire(mutexVal.mutex, FuriWaitForever);
+                    if (mutexVal.zoom != 3) mutexVal.zoom++;
+
+                    screenRefresh = 1;
+                    furi_mutex_release(mutexVal.mutex);
+                }
+                else if((event.input.key == InputKeyDown && event.input.type == InputTypeLong))
+                {
+                    furi_mutex_acquire(mutexVal.mutex, FuriWaitForever);
+                    if (mutexVal.version == 0) mutexVal.version = 1;
+                    else mutexVal.version = 0;
+
+                    screenRefresh = 1;
+                    furi_mutex_release(mutexVal.mutex);
+                }
+            }
+            else if (event.type == ClockEventTypeTick)
+            {
+                if (recordData == 1)
+                {
+                    furi_string_printf(dataString, "%lu,%lu\n", epoch++, counter);
+                    stream_write_string(file_stream, dataString);
+                }
+
+                furi_mutex_acquire(mutexVal.mutex, FuriWaitForever);
+
+                mutexVal.line[mutexVal.newLinePosition] = counter;
+                mutexVal.cps = counter;
+                counter = 0;
+
+                mutexVal.cpm = mutexVal.line[mutexVal.newLinePosition];
+                uint32_t max = mutexVal.line[mutexVal.newLinePosition];
+                uint8_t linePosition = mutexVal.newLinePosition;
+
+                for (int i=1;i<SCREEN_SIZE_X;i++)
+                {
+                    if (linePosition != 0) linePosition--;
+                    else linePosition = SCREEN_SIZE_X - 1;
+
+                    if (i < 60) mutexVal.cpm += mutexVal.line[linePosition];
+                    if (mutexVal.line[linePosition] > max) max = mutexVal.line[linePosition];
+                }
+
+                if (max > 0) mutexVal.coef = ((float)(SCREEN_SIZE_Y-15))/((float)max);
+                else mutexVal.coef = 1;
+
+                if (mutexVal.newLinePosition != SCREEN_SIZE_X - 1) mutexVal.newLinePosition++;
+                else mutexVal.newLinePosition = 0;
+
+                screenRefresh = 1;
+                furi_mutex_release(mutexVal.mutex);
+            }
+            else if (event.type == EventGPIO)
+            {
+                counter++;
+            }
+        }
+
+        if (screenRefresh == 1) view_port_update(view_port);
+    }
+
+    if (recordData == 1) 
+    {
+        buffered_file_stream_close(file_stream);
+        notification_message(notification, &sequence_reset_red);
+    }
+
+    furi_string_free(dataString);
+    furi_record_close(RECORD_NOTIFICATION);
+    stream_free(file_stream);
+    furi_record_close(RECORD_STORAGE);
+
+    furi_hal_power_disable_otg();
+
+    furi_hal_gpio_disable_int_callback(&gpio_ext_pa7);
+    furi_hal_gpio_remove_int_callback(&gpio_ext_pa7);
+    furi_hal_pwm_stop(FuriHalPwmOutputIdLptim2PA4);
+
+    furi_message_queue_free(event_queue);
+    furi_mutex_free(mutexVal.mutex);
+    gui_remove_view_port(gui, view_port);
+    view_port_free(view_port);
+    furi_timer_free(timer);
+    furi_record_close(RECORD_GUI);
+
+    return 0;
+}

binární
.modules/geiger/flipper_geiger/geiger.png


binární
.modules/geiger/img/dice.jpg


binární
.modules/geiger/img/flipper1.png


binární
.modules/geiger/img/flipper10.png


binární
.modules/geiger/img/flipper11.png


binární
.modules/geiger/img/flipper12.png


binární
.modules/geiger/img/flipper13.png


binární
.modules/geiger/img/flipper14.png


binární
.modules/geiger/img/flipper2.png


binární
.modules/geiger/img/flipper3.png


binární
.modules/geiger/img/flipper4.png


binární
.modules/geiger/img/flipper5.png


binární
.modules/geiger/img/flipper6.png


binární
.modules/geiger/img/flipper7.png


binární
.modules/geiger/img/flipper8.png


binární
.modules/geiger/img/flipper9.png


binární
.modules/geiger/img/jack.png


binární
.modules/geiger/img/logo.jpg


binární
.modules/geiger/img/schema.jpg


binární
.modules/geiger/img/user/IMG-1552.jpg


binární
.modules/geiger/img/user/abQXuEz.jpg


binární
.modules/geiger/img/user/testing-of-the-geiger-counter-v0-3zv9gdq4nt0b1.jpg


binární
.modules/geiger/img/version.png


binární
.modules/geiger/img/zoom0.png


binární
.modules/geiger/img/zoom1.png


binární
.modules/geiger/img/zoom2.png


binární
.modules/geiger/img/zoom3.png