From 4992c89630a1052342ab10cd5a580e55b4c21146 Mon Sep 17 00:00:00 2001 From: jbohack Date: Mon, 9 Jan 2023 15:34:18 -0500 Subject: [PATCH] updated protoview & usb_hid_autofire --- applications/plugins/protoview/README.md | 2 +- applications/plugins/protoview/TODO | 4 + applications/plugins/protoview/app.c | 409 ++++------------- applications/plugins/protoview/app.h | 85 +++- applications/plugins/protoview/app_buffer.c | 6 + applications/plugins/protoview/app_buffer.h | 1 + applications/plugins/protoview/app_subghz.c | 26 +- .../plugins/protoview/application.fam | 2 +- .../plugins/protoview/binaries/protoview.fap | Bin 15180 -> 21208 bytes .../plugins/protoview/custom_presets.h | 132 ++++++ .../plugins/protoview/protocols/b4b1.c | 44 ++ .../plugins/protoview/protocols/oregon2.c | 65 +++ .../plugins/protoview/protocols/oregon2.txt | 6 + .../protoview/protocols/renault_tpms.c | 63 +++ applications/plugins/protoview/signal.c | 420 ++++++++++++++++++ applications/plugins/protoview/ui.c | 30 ++ .../plugins/protoview/view_direct_sampling.c | 46 ++ applications/plugins/protoview/view_info.c | 41 ++ .../plugins/protoview/view_raw_signal.c | 97 ++++ .../plugins/protoview/view_settings.c | 93 ++++ .../plugins/usb_hid_autofire/CHANGELOG.md | 3 + .../plugins/usb_hid_autofire/application.fam | 4 +- applications/plugins/usb_hid_autofire/tools.c | 56 +++ applications/plugins/usb_hid_autofire/tools.h | 7 + .../usb_hid_autofire/usb_hid_autofire.c | 29 +- .../plugins/usb_hid_autofire/version.h | 2 +- 26 files changed, 1324 insertions(+), 349 deletions(-) create mode 100644 applications/plugins/protoview/custom_presets.h create mode 100644 applications/plugins/protoview/protocols/b4b1.c create mode 100644 applications/plugins/protoview/protocols/oregon2.c create mode 100644 applications/plugins/protoview/protocols/oregon2.txt create mode 100644 applications/plugins/protoview/protocols/renault_tpms.c create mode 100644 applications/plugins/protoview/signal.c create mode 100644 applications/plugins/protoview/ui.c create mode 100644 applications/plugins/protoview/view_direct_sampling.c create mode 100644 applications/plugins/protoview/view_info.c create mode 100644 applications/plugins/protoview/view_raw_signal.c create mode 100644 applications/plugins/protoview/view_settings.c create mode 100644 applications/plugins/usb_hid_autofire/tools.c create mode 100644 applications/plugins/usb_hid_autofire/tools.h diff --git a/applications/plugins/protoview/README.md b/applications/plugins/protoview/README.md index e7209d7d6..4b8a3c3a0 100644 --- a/applications/plugins/protoview/README.md +++ b/applications/plugins/protoview/README.md @@ -78,7 +78,7 @@ cd ~/flipperZero/official/ git clone --recursive https://github.com/flipperdevices/flipperzero-firmware.git ./ ./fbt ``` -* Copy this application folder in `official/application_user`. +* Copy this application folder in `official/applications_user`. * Connect your Flipper via USB. * Build and install with: `./fbt launch_app APPSRC=protoview`. diff --git a/applications/plugins/protoview/TODO b/applications/plugins/protoview/TODO index bc66ee75a..0003ccd38 100644 --- a/applications/plugins/protoview/TODO +++ b/applications/plugins/protoview/TODO @@ -3,9 +3,13 @@ Core improvements - Detection of non Manchester and non RZ encoded signals. Not sure if there are any signals that are not self clocked widely used in RF. Note that the current approach already detects encodings using short high + long low and long high + short low to encode 0 and 1. In addition to the current classifier, it is possible to add one that checks for a sequence of pulses that are all multiples of some base length. This should detect, for instance, even NRZ encodings where 1 and 0 are just clocked as they are. +- Views on-enter on-exit. + Features ======== +- Help screen (with press ok for next page). +- Detect the line code used and try to decode the message as hex dump. - Pressing right/left you browse different modes: * Current best signal pulse classes. * Raw square wave display. Central button freezes and resumes (toggle). When frozen we display "paused" (inverted) on the low part of the screen. diff --git a/applications/plugins/protoview/app.c b/applications/plugins/protoview/app.c index 10c3fe599..4e6b2ff3e 100644 --- a/applications/plugins/protoview/app.c +++ b/applications/plugins/protoview/app.c @@ -1,257 +1,42 @@ /* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved * See the LICENSE file for information about the license. */ -#include -#include -#include -#include -#include -#include #include "app.h" -#include "app_buffer.h" + +/* If this define is enabled, ProtoView is going to mess with the + * otherwise opaque SubGhzWorker structure in order to disable + * its filter for samples shorter than a given amount (30us at the + * time I'm writing this comment). + * + * This structure must be taken in sync with the one of the firmware. */ +#define PROTOVIEW_DISABLE_SUBGHZ_FILTER 0 + +#ifdef PROTOVIEW_DISABLE_SUBGHZ_FILTER +struct SubGhzWorker { + FuriThread* thread; + FuriStreamBuffer* stream; + + volatile bool running; + volatile bool overrun; + + LevelDuration filter_level_duration; + bool filter_running; + uint16_t filter_duration; + + SubGhzWorkerOverrunCallback overrun_callback; + SubGhzWorkerPairCallback pair_callback; + void* context; +}; +#endif RawSamplesBuffer *RawSamples, *DetectedSamples; extern const SubGhzProtocolRegistry protoview_protocol_registry; -/* Render the received signal. - * - * The screen of the flipper is 128 x 64. Even using 4 pixels per line - * (where low level signal is one pixel high, high level is 4 pixels - * high) and 4 pixels of spacing between the different lines, we can - * plot comfortably 8 lines. - * - * The 'idx' argument is the first sample to render in the circular - * buffer. */ -void render_signal(ProtoViewApp *app, Canvas *const canvas, RawSamplesBuffer *buf, uint32_t idx) { - canvas_set_color(canvas, ColorBlack); - - int rows = 8; - uint32_t time_per_pixel = app->us_scale; - bool level = 0; - uint32_t dur = 0, sample_num = 0; - for (int row = 0; row < rows ; row++) { - for (int x = 0; x < 128; x++) { - int y = 3 + row*8; - if (dur < time_per_pixel/2) { - /* Get more data. */ - raw_samples_get(buf, idx++, &level, &dur); - sample_num++; - } - - canvas_draw_line(canvas, x,y,x,y-(level*3)); - - /* Write a small triangle under the last sample detected. */ - if (app->signal_bestlen != 0 && - sample_num == app->signal_bestlen+1) - { - canvas_draw_dot(canvas,x,y+2); - canvas_draw_dot(canvas,x-1,y+3); - canvas_draw_dot(canvas,x,y+3); - canvas_draw_dot(canvas,x+1,y+3); - sample_num++; /* Make sure we don't mark the next, too. */ - } - - /* Remove from the current level duration the time we - * just plot. */ - if (dur > time_per_pixel) - dur -= time_per_pixel; - else - dur = 0; - } - } -} - -/* Return the time difference between a and b, always >= 0 since - * the absolute value is returned. */ -uint32_t duration_delta(uint32_t a, uint32_t b) { - return a > b ? a - b : b - a; -} - -/* This function starts scanning samples at offset idx looking for the - * longest run of pulses, either high or low, that are among 10% - * of each other, for a maximum of three classes. The classes are - * counted separtely for high and low signals (RF on / off) because - * many devices tend to have different pulse lenghts depending on - * the level of the pulse. - * - * For instance Oregon2 sensors, in the case of protocol 2.1 will send - * pulses of ~400us (RF on) VS ~580us (RF off). */ -#define SEARCH_CLASSES 3 -uint32_t search_coherent_signal(RawSamplesBuffer *s, uint32_t idx) { - struct { - uint32_t dur[2]; /* dur[0] = low, dur[1] = high */ - uint32_t count[2]; /* Associated observed frequency. */ - } classes[SEARCH_CLASSES]; - - memset(classes,0,sizeof(classes)); - uint32_t minlen = 40, maxlen = 4000; /* Depends on data rate, here we - allow for high and low. */ - uint32_t len = 0; /* Observed len of coherent samples. */ - s->short_pulse_dur = 0; - for (uint32_t j = idx; j < idx+500; j++) { - bool level; - uint32_t dur; - raw_samples_get(s, j, &level, &dur); - if (dur < minlen || dur > maxlen) break; /* return. */ - - /* Let's see if it matches a class we already have or if we - * can populate a new (yet empty) class. */ - uint32_t k; - for (k = 0; k < SEARCH_CLASSES; k++) { - if (classes[k].count[level] == 0) { - classes[k].dur[level] = dur; - classes[k].count[level] = 1; - break; /* Sample accepted. */ - } else { - uint32_t classavg = classes[k].dur[level]; - uint32_t count = classes[k].count[level]; - uint32_t delta = duration_delta(dur,classavg); - if (delta < classavg/10) { - /* It is useful to compute the average of the class - * we are observing. We know how many samples we got so - * far, so we can recompute the average easily. - * By always having a better estimate of the pulse len - * we can avoid missing next samples in case the first - * observed samples are too off. */ - classavg = ((classavg * count) + dur) / (count+1); - classes[k].dur[level] = classavg; - classes[k].count[level]++; - break; /* Sample accepted. */ - } - } - } - - if (k == SEARCH_CLASSES) break; /* No match, return. */ - - /* If we are here, we accepted this sample. Try with the next - * one. */ - len++; - } - - /* Update the buffer setting the shortest pulse we found - * among the three classes. This will be used when scaling - * for visualization. */ - for (int j = 0; j < SEARCH_CLASSES; j++) { - for (int level = 0; level < 2; level++) { - if (classes[j].dur[level] == 0) continue; - if (classes[j].count[level] < 3) continue; - if (s->short_pulse_dur == 0 || - s->short_pulse_dur > classes[j].dur[level]) - { - s->short_pulse_dur = classes[j].dur[level]; - } - } - } - return len; -} - -/* Search the buffer with the stored signal (last N samples received) - * in order to find a coherent signal. If a signal that does not appear to - * be just noise is found, it is set in DetectedSamples global signal - * buffer, that is what is rendered on the screen. */ -void scan_for_signal(ProtoViewApp *app) { - /* We need to work on a copy: the RawSamples buffer is populated - * by the background thread receiving data. */ - RawSamplesBuffer *copy = raw_samples_alloc(); - raw_samples_copy(copy,RawSamples); - - /* Try to seek on data that looks to have a regular high low high low - * pattern. */ - uint32_t minlen = 13; /* Min run of coherent samples. Up to - 12 samples it's very easy to mistake - noise for signal. */ - - uint32_t i = 0; - while (i < copy->total-1) { - uint32_t thislen = search_coherent_signal(copy,i); - if (thislen > minlen && thislen > app->signal_bestlen) { - app->signal_bestlen = thislen; - raw_samples_copy(DetectedSamples,copy); - DetectedSamples->idx = (DetectedSamples->idx+i)% - DetectedSamples->total; - FURI_LOG_E(TAG, "Displayed sample updated (%d samples)", - (int)thislen); - } - i += thislen ? thislen : 1; - } - raw_samples_free(copy); -} - /* Draw some text with a border. If the outside color is black and the inside * color is white, it just writes the border of the text, but the function can * also be used to write a bold variation of the font setting both the * colors to black, or alternatively to write a black text with a white * border so that it is visible if there are black stuff on the background. */ -void canvas_draw_str_with_border(Canvas* canvas, uint8_t x, uint8_t y, const char* str, Color text_color, Color border_color) -{ - struct { - uint8_t x; uint8_t y; - } dir[8] = { - {-1,-1}, - {0,-1}, - {1,-1}, - {1,0}, - {1,1}, - {0,1}, - {-1,1}, - {-1,0} - }; - - /* Rotate in all the directions writing the same string to create a - * border, then write the actual string in the other color in the - * middle. */ - canvas_set_color(canvas, border_color); - for (int j = 0; j < 8; j++) - canvas_draw_str(canvas,x+dir[j].x,y+dir[j].y,str); - canvas_set_color(canvas, text_color); - canvas_draw_str(canvas,x,y,str); - canvas_set_color(canvas, ColorBlack); -} - -/* Raw pulses rendering. This is our default view. */ -void render_view_raw_pulses(Canvas *const canvas, ProtoViewApp *app) { - /* Show signal. */ - render_signal(app, canvas, DetectedSamples, 0); - - /* Show signal information. */ - char buf[64]; - snprintf(buf,sizeof(buf),"%luus", - (unsigned long)DetectedSamples->short_pulse_dur); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_with_border(canvas, 97, 63, buf, ColorWhite, ColorBlack); -} - -/* Renders a single view with frequency and modulation setting. However - * this are logically two different views, and only one of the settings - * will be highlighted. */ -void render_view_settings(Canvas *const canvas, ProtoViewApp *app) { - UNUSED(app); - canvas_set_font(canvas, FontPrimary); - if (app->current_view == ViewFrequencySettings) - canvas_draw_str_with_border(canvas,1,10,"Frequency",ColorWhite,ColorBlack); - else - canvas_draw_str(canvas,1,10,"Frequency"); - - if (app->current_view == ViewModulationSettings) - canvas_draw_str_with_border(canvas,70,10,"Modulation",ColorWhite,ColorBlack); - else - canvas_draw_str(canvas,70,10,"Modulation"); - canvas_set_font(canvas, FontSecondary); - canvas_draw_str(canvas,10,61,"Use up and down to modify"); - - /* Show frequency. We can use big numbers font since it's just a number. */ - if (app->current_view == ViewFrequencySettings) { - char buf[16]; - snprintf(buf,sizeof(buf),"%.2f",(double)app->frequency/1000000); - canvas_set_font(canvas, FontBigNumbers); - canvas_draw_str(canvas, 30, 40, buf); - } else if (app->current_view == ViewModulationSettings) { - int current = app->modulation; - canvas_set_font(canvas, FontPrimary); - canvas_draw_str(canvas, 33, 39, ProtoViewModulations[current].name); - } -} - /* The callback actually just passes the control to the actual active * view callback, after setting up basic stuff like cleaning the screen * and setting color to black. */ @@ -267,9 +52,11 @@ static void render_callback(Canvas *const canvas, void *ctx) { /* Call who is in charge right now. */ switch(app->current_view) { case ViewRawPulses: render_view_raw_pulses(canvas,app); break; + case ViewInfo: render_view_info(canvas,app); break; case ViewFrequencySettings: case ViewModulationSettings: render_view_settings(canvas,app); break; + case ViewDirectSampling: render_view_direct_sampling(canvas,app); break; case ViewLast: furi_crash(TAG " ViewLast selected"); break; } } @@ -279,11 +66,29 @@ static void render_callback(Canvas *const canvas, void *ctx) { static void input_callback(InputEvent* input_event, void* ctx) { ProtoViewApp *app = ctx; + furi_message_queue_put(app->event_queue,input_event,FuriWaitForever); +} - if (input_event->type == InputTypePress) { - furi_message_queue_put(app->event_queue,input_event,FuriWaitForever); - FURI_LOG_E(TAG, "INPUT CALLBACK %d", (int)input_event->key); + +/* Called to switch view (when left/right is pressed). Handles + * changing the current view ID and calling the enter/exit view + * callbacks if needed. */ +static void app_switch_view(ProtoViewApp *app, SwitchViewDirection dir) { + ProtoViewCurrentView old = app->current_view; + if (dir == AppNextView) { + app->current_view++; + if (app->current_view == ViewLast) app->current_view = 0; + } else if (dir == AppPrevView) { + if (app->current_view == 0) + app->current_view = ViewLast-1; + else + app->current_view--; } + ProtoViewCurrentView new = app->current_view; + + /* Call the enter/exit view callbacks if needed. */ + if (old == ViewDirectSampling) view_exit_direct_sampling(app); + if (new == ViewDirectSampling) view_enter_direct_sampling(app); } /* Allocate the application state and initialize a number of stuff. @@ -297,7 +102,7 @@ ProtoViewApp* protoview_app_alloc() { //init setting app->setting = subghz_setting_alloc(); - subghz_setting_load(app->setting, EXT_PATH("protoview/settings.txt")); + subghz_setting_load(app->setting, EXT_PATH("subghz/assets/setting_user")); // GUI app->gui = furi_record_open(RECORD_GUI); @@ -310,13 +115,20 @@ ProtoViewApp* protoview_app_alloc() { // Signal found and visualization defaults app->signal_bestlen = 0; - app->us_scale = 100; + app->signal_decoded = false; + app->us_scale = PROTOVIEW_RAW_VIEW_DEFAULT_SCALE; + app->signal_offset = 0; //init Worker & Protocol app->txrx = malloc(sizeof(ProtoViewTxRx)); /* Setup rx worker and environment. */ app->txrx->worker = subghz_worker_alloc(); + +#ifdef PROTOVIEW_DISABLE_SUBGHZ_FILTER + app->txrx->worker->filter_running = 0; +#endif + app->txrx->environment = subghz_environment_alloc(); subghz_environment_set_protocol_registry( app->txrx->environment, (void*)&protoview_protocol_registry); @@ -380,70 +192,6 @@ static void timer_callback(void *ctx) { scan_for_signal(app); } -/* Handle input for the raw pulses view. */ -void process_input_raw_pulses(ProtoViewApp *app, InputEvent input) { - if (input.key == InputKeyOk) { - /* Reset the current sample to capture the next. */ - app->signal_bestlen = 0; - raw_samples_reset(DetectedSamples); - raw_samples_reset(RawSamples); - } else if (input.key == InputKeyDown) { - /* Rescaling. The set becomes finer under 50us per pixel. */ - uint32_t scale_step = app->us_scale >= 50 ? 50 : 10; - if (app->us_scale < 500) app->us_scale += scale_step; - } else if (input.key == InputKeyUp) { - uint32_t scale_step = app->us_scale > 50 ? 50 : 10; - if (app->us_scale > 10) app->us_scale -= scale_step; - } -} - -/* Handle input for the settings view. */ -void process_input_settings(ProtoViewApp *app, InputEvent input) { - /* Here we handle only up and down. Avoid any work if the user - * pressed something else. */ - if (input.key != InputKeyDown && input.key != InputKeyUp) return; - - if (app->current_view == ViewFrequencySettings) { - size_t curidx = 0, i; - size_t count = subghz_setting_get_frequency_count(app->setting); - - /* Scan the list of frequencies to check for the index of the - * currently set frequency. */ - for(i = 0; i < count; i++) { - uint32_t freq = subghz_setting_get_frequency(app->setting,i); - if (freq == app->frequency) { - curidx = i; - break; - } - } - if (i == count) return; /* Should never happen. */ - - if (input.key == InputKeyUp) { - curidx = (curidx+1) % count; - } else if (input.key == InputKeyDown) { - curidx = curidx == 0 ? count-1 : curidx-1; - } - app->frequency = subghz_setting_get_frequency(app->setting,curidx); - } else if (app->current_view == ViewModulationSettings) { - uint32_t count = 0; - uint32_t modid = app->modulation; - - while(ProtoViewModulations[count].name != NULL) count++; - if (input.key == InputKeyUp) { - modid = (modid+1) % count; - } else if (input.key == InputKeyDown) { - modid = modid == 0 ? count-1 : modid-1; - } - app->modulation = modid; - } - - /* Apply changes. */ - FURI_LOG_E(TAG, "Setting view, setting frequency/modulation to %lu %s", app->frequency, ProtoViewModulations[app->modulation].name); - radio_rx_end(app); - radio_begin(app); - radio_rx(app); -} - int32_t protoview_app_entry(void* p) { UNUSED(p); ProtoViewApp *app = protoview_app_alloc(); @@ -465,39 +213,53 @@ int32_t protoview_app_entry(void* p) { while(app->running) { FuriStatus qstat = furi_message_queue_get(app->event_queue, &input, 100); if (qstat == FuriStatusOk) { - FURI_LOG_E(TAG, "Main Loop - Input: %u", input.key); + if (DEBUG_MSG) FURI_LOG_E(TAG, "Main Loop - Input: type %d key %u", + input.type, input.key); /* Handle navigation here. Then handle view-specific inputs * in the view specific handling function. */ - if (input.key == InputKeyBack) { + if (input.type == InputTypeShort && + input.key == InputKeyBack) + { /* Exit the app. */ app->running = 0; - } else if (input.key == InputKeyRight) { + } else if (input.type == InputTypeShort && + input.key == InputKeyRight) + { /* Go to the next view. */ - app->current_view++; - if (app->current_view == ViewLast) app->current_view = 0; - } else if (input.key == InputKeyLeft) { + app_switch_view(app,AppNextView); + } else if (input.type == InputTypeShort && + input.key == InputKeyLeft) + { /* Go to the previous view. */ - if (app->current_view == 0) - app->current_view = ViewLast-1; - else - app->current_view--; + app_switch_view(app,AppPrevView); } else { + /* This is where we pass the control to the currently + * active view input processing. */ switch(app->current_view) { case ViewRawPulses: process_input_raw_pulses(app,input); break; + case ViewInfo: + process_input_info(app,input); + break; case ViewFrequencySettings: case ViewModulationSettings: process_input_settings(app,input); break; + case ViewDirectSampling: + process_input_direct_sampling(app,input); + break; case ViewLast: furi_crash(TAG " ViewLast selected"); break; } } } else { - static int c = 0; - c++; - if (!(c % 20)) FURI_LOG_E(TAG, "Loop timeout"); + /* Useful to understand if the app is still alive when it + * does not respond because of bugs. */ + if (DEBUG_MSG) { + static int c = 0; c++; + if (!(c % 20)) FURI_LOG_E(TAG, "Loop timeout"); + } } view_port_update(app->view_port); } @@ -513,3 +275,4 @@ int32_t protoview_app_entry(void* p) { protoview_app_free(app); return 0; } + diff --git a/applications/plugins/protoview/app.h b/applications/plugins/protoview/app.h index 18852a14a..4ad0a9196 100644 --- a/applications/plugins/protoview/app.h +++ b/applications/plugins/protoview/app.h @@ -3,6 +3,11 @@ #pragma once +#include +#include +#include +#include +#include #include #include #include @@ -15,8 +20,13 @@ #include #include #include +#include "app_buffer.h" #define TAG "ProtoView" +#define PROTOVIEW_RAW_VIEW_DEFAULT_SCALE 100 +#define BITMAP_SEEK_NOT_FOUND UINT32_MAX + +#define DEBUG_MSG 1 typedef struct ProtoViewApp ProtoViewApp; @@ -30,14 +40,23 @@ typedef enum { /* Currently active view. */ typedef enum { ViewRawPulses, + ViewInfo, ViewFrequencySettings, ViewModulationSettings, + ViewDirectSampling, ViewLast, /* Just a sentinel to wrap around. */ } ProtoViewCurrentView; +/* Used by app_switch_view() */ +typedef enum { + AppNextView, + AppPrevView +} SwitchViewDirection; + typedef struct { const char *name; FuriHalSubGhzPreset preset; + uint8_t *custom; } ProtoViewModulation; extern ProtoViewModulation ProtoViewModulations[]; /* In app_subghz.c */ @@ -54,6 +73,21 @@ struct ProtoViewTxRx { typedef struct ProtoViewTxRx ProtoViewTxRx; +/* This stucture is filled by the decoder for specific protocols with the + * informations about the message. ProtoView will display such information + * in the message info view. */ +#define PROTOVIEW_MSG_STR_LEN 32 +typedef struct ProtoViewMsgInfo { + char name[PROTOVIEW_MSG_STR_LEN]; /* Protocol name and version. */ + char raw[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific raw representation.*/ + /* The following is what the decoder wants to show to user. Each decoder + * can use the number of fileds it needs. */ + char info1[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 1. */ + char info2[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 2. */ + char info3[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 3. */ + uint64_t len; /* Bits consumed from the stream. */ +} ProtoViewMsgInfo; + struct ProtoViewApp { /* GUI */ Gui *gui; @@ -66,17 +100,66 @@ struct ProtoViewApp { ProtoViewTxRx *txrx; /* Radio state. */ SubGhzSetting *setting; /* A list of valid frequencies. */ - /* Application state and config. */ + /* Generic app state. */ int running; /* Once false exists the app. */ uint32_t signal_bestlen; /* Longest coherent signal observed so far. */ + bool signal_decoded; /* Was the current signal decoded? */ + ProtoViewMsgInfo signal_info; /* Decoded message, if signal_decoded true. */ + + /* Raw view apps state. */ uint32_t us_scale; /* microseconds per pixel. */ + uint32_t signal_offset; /* Long press left/right panning in raw view. */ + + /* Configuration view app state. */ uint32_t frequency; /* Current frequency. */ uint8_t modulation; /* Current modulation ID, array index in the ProtoViewModulations table. */ }; +typedef struct ProtoViewDecoder { + const char *name; /* Protocol name. */ + /* The decode function takes a buffer that is actually a bitmap, with + * high and low levels represented as 0 and 1. The number of high/low + * pulses represented by the bitmap is passed as the 'numbits' argument, + * while 'numbytes' represents the total size of the bitmap pointed by + * 'bits'. So 'numbytes' is mainly useful to pass as argument to other + * functions that perform bit extraction with bound checking, such as + * bitmap_get() and so forth. */ + bool (*decode)(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info); +} ProtoViewDecoder; + +extern RawSamplesBuffer *RawSamples, *DetectedSamples; + +/* app_radio.c */ void radio_begin(ProtoViewApp* app); uint32_t radio_rx(ProtoViewApp* app); void radio_idle(ProtoViewApp* app); void radio_rx_end(ProtoViewApp* app); void radio_sleep(ProtoViewApp* app); + +/* signal.c */ +uint32_t duration_delta(uint32_t a, uint32_t b); +void reset_current_signal(ProtoViewApp *app); +void scan_for_signal(ProtoViewApp *app); +bool bitmap_get(uint8_t *b, uint32_t blen, uint32_t bitpos); +void bitmap_set(uint8_t *b, uint32_t blen, uint32_t bitpos, bool val); +void bitmap_set_pattern(uint8_t *b, uint32_t blen, const char *pat); +void bitmap_invert_bytes_bits(uint8_t *p, uint32_t len); +bool bitmap_match_bits(uint8_t *b, uint32_t blen, uint32_t bitpos, const char *bits); +uint32_t bitmap_seek_bits(uint8_t *b, uint32_t blen, uint32_t startpos, uint32_t maxbits, const char *bits); +uint32_t convert_from_line_code(uint8_t *buf, uint64_t buflen, uint8_t *bits, uint32_t len, uint32_t offset, const char *zero_pattern, const char *one_pattern); + +/* view_*.c */ +void render_view_raw_pulses(Canvas *const canvas, ProtoViewApp *app); +void process_input_raw_pulses(ProtoViewApp *app, InputEvent input); +void render_view_settings(Canvas *const canvas, ProtoViewApp *app); +void process_input_settings(ProtoViewApp *app, InputEvent input); +void render_view_info(Canvas *const canvas, ProtoViewApp *app); +void process_input_info(ProtoViewApp *app, InputEvent input); +void render_view_direct_sampling(Canvas *const canvas, ProtoViewApp *app); +void process_input_direct_sampling(ProtoViewApp *app, InputEvent input); +void view_enter_direct_sampling(ProtoViewApp *app); +void view_exit_direct_sampling(ProtoViewApp *app); + +/* ui.c */ +void canvas_draw_str_with_border(Canvas* canvas, uint8_t x, uint8_t y, const char* str, Color text_color, Color border_color); diff --git a/applications/plugins/protoview/app_buffer.c b/applications/plugins/protoview/app_buffer.c index 0061a4142..df2e7074e 100644 --- a/applications/plugins/protoview/app_buffer.c +++ b/applications/plugins/protoview/app_buffer.c @@ -34,6 +34,12 @@ void raw_samples_reset(RawSamplesBuffer *s) { furi_mutex_release(s->mutex); } +/* Set the raw sample internal index so that what is currently at + * offset 'offset', will appear to be at 0 index. */ +void raw_samples_center(RawSamplesBuffer *s, uint32_t offset) { + s->idx = (s->idx+offset) % RAW_SAMPLES_NUM; +} + /* Add the specified sample in the circular buffer. */ void raw_samples_add(RawSamplesBuffer *s, bool level, uint32_t dur) { furi_mutex_acquire(s->mutex,FuriWaitForever); diff --git a/applications/plugins/protoview/app_buffer.h b/applications/plugins/protoview/app_buffer.h index 90d186f89..5d997d02e 100644 --- a/applications/plugins/protoview/app_buffer.h +++ b/applications/plugins/protoview/app_buffer.h @@ -23,6 +23,7 @@ typedef struct RawSamplesBuffer { RawSamplesBuffer *raw_samples_alloc(void); void raw_samples_reset(RawSamplesBuffer *s); +void raw_samples_center(RawSamplesBuffer *s, uint32_t offset); void raw_samples_add(RawSamplesBuffer *s, bool level, uint32_t dur); void raw_samples_get(RawSamplesBuffer *s, uint32_t idx, bool *level, uint32_t *dur); void raw_samples_copy(RawSamplesBuffer *dst, RawSamplesBuffer *src); diff --git a/applications/plugins/protoview/app_subghz.c b/applications/plugins/protoview/app_subghz.c index e89cab214..2bb87af7a 100644 --- a/applications/plugins/protoview/app_subghz.c +++ b/applications/plugins/protoview/app_subghz.c @@ -2,17 +2,20 @@ * See the LICENSE file for information about the license. */ #include "app.h" +#include "custom_presets.h" #include ProtoViewModulation ProtoViewModulations[] = { - {"OOK 650Khz", FuriHalSubGhzPresetOok650Async}, - {"OOK 270Khz", FuriHalSubGhzPresetOok270Async}, - {"2FSK 2.38Khz", FuriHalSubGhzPreset2FSKDev238Async}, - {"2FSK 47.6Khz", FuriHalSubGhzPreset2FSKDev476Async}, - {"MSK", FuriHalSubGhzPresetMSK99_97KbAsync}, - {"GFSK", FuriHalSubGhzPresetGFSK9_99KbAsync}, - {NULL, 0} /* End of list sentinel. */ + {"OOK 650Khz", FuriHalSubGhzPresetOok650Async, NULL}, + {"OOK 270Khz", FuriHalSubGhzPresetOok270Async, NULL}, + {"2FSK 2.38Khz", FuriHalSubGhzPreset2FSKDev238Async, NULL}, + {"2FSK 47.6Khz", FuriHalSubGhzPreset2FSKDev476Async, NULL}, + {"MSK", FuriHalSubGhzPresetMSK99_97KbAsync, NULL}, + {"GFSK", FuriHalSubGhzPresetGFSK9_99KbAsync, NULL}, + {"TPMS 1 (FSK)", 0, (uint8_t*)protoview_subghz_tpms1_async_regs}, + {"TPMS 2 (FSK)", 0, (uint8_t*)protoview_subghz_tpms2_async_regs}, + {NULL, 0, NULL} /* End of list sentinel. */ }; /* Called after the application initialization in order to setup the @@ -23,7 +26,14 @@ void radio_begin(ProtoViewApp* app) { furi_assert(app); furi_hal_subghz_reset(); furi_hal_subghz_idle(); - furi_hal_subghz_load_preset(ProtoViewModulations[app->modulation].preset); + + /* The CC1101 preset can be either one of the standard presets, if + * the modulation "custom" field is NULL, or a custom preset we + * defined in custom_presets.h. */ + if (ProtoViewModulations[app->modulation].custom == NULL) + furi_hal_subghz_load_preset(ProtoViewModulations[app->modulation].preset); + else + furi_hal_subghz_load_custom_preset(ProtoViewModulations[app->modulation].custom); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); app->txrx->txrx_state = TxRxStateIDLE; } diff --git a/applications/plugins/protoview/application.fam b/applications/plugins/protoview/application.fam index 5b3589ce7..df2a77261 100644 --- a/applications/plugins/protoview/application.fam +++ b/applications/plugins/protoview/application.fam @@ -5,7 +5,7 @@ App( entry_point="protoview_app_entry", cdefines=["APP_PROTOVIEW"], requires=["gui"], - stack_size=8 * 1024, + stack_size=8*1024, order=50, fap_icon="appicon.png", fap_category="Tools", diff --git a/applications/plugins/protoview/binaries/protoview.fap b/applications/plugins/protoview/binaries/protoview.fap index 9e1f9c7ec4d853401b2e0937634f17ee1af519dd..f5a03056e172efab800adfb989d987ce2e3b849e 100644 GIT binary patch literal 21208 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 literal 15180 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 U=>1}On1IJ-p`0_t-A55w9JcK`qY diff --git a/applications/plugins/protoview/custom_presets.h b/applications/plugins/protoview/custom_presets.h new file mode 100644 index 000000000..d34446f48 --- /dev/null +++ b/applications/plugins/protoview/custom_presets.h @@ -0,0 +1,132 @@ +#include + +/* This is how to configure registers MDMCFG3 and MDMCFG4. + * + * Data rate kBaud setting: + * + * MDMCFG3 is the data rate mantissa, the exponent is in MDMCFG4, + * last 4 bits of the register. + * + * The rate (assuming 26Mhz crystal) is calculated as follows: + * + * ((256+MDMCFG3)*(2^MDMCFG4:0..3bits)) / 2^28 * 26000000. + * + * For instance for the default values of MDMCFG3 (34) and MDMCFG4 (12): + * + * ((256+34)*(2^12))/(2^28)*26000000 = 115051.2688000000, that is 115KBaud + * + * Bandwidth filter setting: + * + * BW filter as just 16 possibilities depending on how the first nibble + * (first 4 bits) of the MDMCFG4 bits are set. Instead of providing the + * formula, it is simpler to show all the values of the nibble and the + * corresponding bandwidth filter. + * + * 0 812khz + * 1 650khz + * 2 541khz + * 3 464khz + * 4 406khz + * 5 325khz + * 6 270khz + * 7 232khz + * 8 203khz + * 9 162khz + * a 135khz + * b 116khz + * c 102khz + * d 82 khz + * e 68 khz + * f 58 khz + */ + +/* 20 KBaud, 2FSK, 28.56 kHz deviation, 325 Khz bandwidth filter. */ +static uint8_t protoview_subghz_tpms1_async_regs[][2] = { + /* GPIO GD0 */ + {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input + + /* Frequency Synthesizer Control */ + {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + /* Packet engine */ + {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening + {CC1101_PKTCTRL1, 0x04}, + + // // Modem Configuration + {CC1101_MDMCFG0, 0x00}, + {CC1101_MDMCFG1, 0x02}, + {CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized). Other code reading TPMS uses GFSK, but should be the same when in RX mode. + {CC1101_MDMCFG3, 0x93}, // Data rate is 20kBaud + {CC1101_MDMCFG4, 0x59}, // Rx bandwidth filter is 325 kHz + {CC1101_DEVIATN, 0x41}, // Deviation 28.56 kHz + + /* Main Radio Control State Machine */ + {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + {CC1101_FOCCFG, + 0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + {CC1101_AGCCTRL0, + 0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary + {CC1101_AGCCTRL1, + 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB + + /* Wake on radio and timeouts control */ + {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + {CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer + {CC1101_FREND1, 0x56}, + + /* End */ + {0, 0}, +}; + +/* 40 KBaud, 2FSK, 19 kHz deviation, 102 Khz bandwidth filter. */ +static uint8_t protoview_subghz_tpms2_async_regs[][2] = { + /* GPIO GD0 */ + {CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input + + /* Frequency Synthesizer Control */ + {CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz + + /* Packet engine */ + {CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening + {CC1101_PKTCTRL1, 0x04}, + + // // Modem Configuration + {CC1101_MDMCFG0, 0x00}, + {CC1101_MDMCFG1, 0x02}, + {CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized). Other code reading TPMS uses GFSK, but should be the same when in RX mode. + {CC1101_MDMCFG3, 0x93}, // Data rate is 40kBaud + {CC1101_MDMCFG4, 0x6A}, // 6 = BW filter 270kHz, A = Data rate exp + {CC1101_DEVIATN, 0x41}, // Deviation 19.042 kHz + + /* Main Radio Control State Machine */ + {CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us) + + /* Frequency Offset Compensation Configuration */ + {CC1101_FOCCFG, + 0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off + + /* Automatic Gain Control */ + {CC1101_AGCCTRL0, + 0x91}, //10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary + {CC1101_AGCCTRL1, + 0x00}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET + {CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB + + /* Wake on radio and timeouts control */ + {CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours + + /* Frontend configuration */ + {CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer + {CC1101_FREND1, 0x56}, + + /* End */ + {0, 0}, +}; + diff --git a/applications/plugins/protoview/protocols/b4b1.c b/applications/plugins/protoview/protocols/b4b1.c new file mode 100644 index 000000000..6977c0eb3 --- /dev/null +++ b/applications/plugins/protoview/protocols/b4b1.c @@ -0,0 +1,44 @@ +/* PT/SC remotes. Usually 443.92 Mhz OOK. + * + * This line code is used in many remotes such as Princeton chips + * named PT, Silian Microelectronics SC5262 and others. + * Basically every 4 pulsee represent a bit, where 1000 means 0, and + * 1110 means 1. Usually we can read 24 bits of data. + * In this specific implementation we check for a prelude that is + * 1 bit high, 31 bits low, but the check is relaxed. */ + +#include "../app.h" + +static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) { + if (numbits < 30) return false; + const char *sync_patterns[3] = { + "10000000000000000000000000000001", /* 30 zero bits. */ + "100000000000000000000000000000001", /* 31 zero bits. */ + "1000000000000000000000000000000001", /* 32 zero bits. */ + }; + + uint32_t off; + int j; + for (j = 0; j < 3; j++) { + off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_patterns[j]); + if (off != BITMAP_SEEK_NOT_FOUND) break; + } + if (off == BITMAP_SEEK_NOT_FOUND) return false; + if (DEBUG_MSG) FURI_LOG_E(TAG, "B4B1 preamble at: %lu",off); + off += strlen(sync_patterns[j])-1; + + uint8_t d[3]; /* 24 bits of data. */ + uint32_t decoded = + convert_from_line_code(d,sizeof(d),bits,numbytes,off,"1000","1110"); + + if (DEBUG_MSG) FURI_LOG_E(TAG, "B4B1 decoded: %lu",decoded); + if (decoded != 24) return false; + snprintf(info->name,PROTOVIEW_MSG_STR_LEN,"PT/SC remote"); + snprintf(info->raw,PROTOVIEW_MSG_STR_LEN,"%02X%02X%02X",d[0],d[1],d[2]); + info->len = off+(4*24); + return true; +} + +ProtoViewDecoder B4B1Decoder = { + "B4B1", decode +}; diff --git a/applications/plugins/protoview/protocols/oregon2.c b/applications/plugins/protoview/protocols/oregon2.c new file mode 100644 index 000000000..3aa57c72d --- /dev/null +++ b/applications/plugins/protoview/protocols/oregon2.c @@ -0,0 +1,65 @@ +/* Oregon remote termometers. Usually 443.92 Mhz OOK. + * + * The protocol is described here: + * https://wmrx00.sourceforge.net/Arduino/OregonScientific-RF-Protocols.pdf + * This implementation is not very complete. */ + +#include "../app.h" + +static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) { + if (numbits < 32) return false; + const char *sync_pattern = "01100110" "01100110" "10010110" "10010110"; + uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern); + if (off == BITMAP_SEEK_NOT_FOUND) return false; + FURI_LOG_E(TAG, "Oregon2 preamble+sync found"); + + off += 32; /* Skip preamble. */ + + uint8_t buffer[8], raw[8] = {0}; + uint32_t decoded = + convert_from_line_code(buffer,sizeof(buffer),bits,numbytes,off,"1001","0110"); + FURI_LOG_E(TAG, "Oregon2 decoded bits: %lu", decoded); + + if (decoded < 11*4) return false; /* Minimum len to extract some data. */ + + char temp[3] = {0}, deviceid[2] = {0}, hum[2] = {0}; + for (int j = 0; j < 64; j += 4) { + uint8_t nib[1]; + nib[0] = (bitmap_get(buffer,8,j+0) | + bitmap_get(buffer,8,j+1) << 1 | + bitmap_get(buffer,8,j+2) << 2 | + bitmap_get(buffer,8,j+3) << 3); + if (DEBUG_MSG) FURI_LOG_E(TAG, "Not inverted nibble[%d]: %x", j/4, (unsigned int)nib[0]); + raw[j/8] |= nib[0] << (4-(j%4)); + switch(j/4) { + case 1: deviceid[0] |= nib[0]; break; + case 0: deviceid[0] |= nib[0] << 4; break; + case 3: deviceid[1] |= nib[0]; break; + case 2: deviceid[1] |= nib[0] << 4; break; + case 10: temp[0] = nib[0]; break; + /* Fixme: take the temperature sign from nibble 11. */ + case 9: temp[1] = nib[0]; break; + case 8: temp[2] = nib[0]; break; + case 13: hum[0] = nib[0]; break; + case 12: hum[1] = nib[0]; break; + } + } + + snprintf(info->name,sizeof(info->name),"%s","Oregon v2.1"); + /* The following line crashes the Flipper because of broken + * snprintf() implementation. */ + snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X", + raw[0],raw[1],raw[2],raw[3],raw[4],raw[5], + raw[6],raw[7]); + snprintf(info->info1,sizeof(info->info1),"Sensor ID %02X%02X", + deviceid[0], deviceid[1]); + snprintf(info->info2,sizeof(info->info2),"Temperature %d%d.%d", + temp[0],temp[1],temp[2]); + snprintf(info->info3,sizeof(info->info3),"Humidity %d%d", + hum[0],hum[1]); + return true; +} + +ProtoViewDecoder Oregon2Decoder = { + "Oregon2", decode +}; diff --git a/applications/plugins/protoview/protocols/oregon2.txt b/applications/plugins/protoview/protocols/oregon2.txt new file mode 100644 index 000000000..362631431 --- /dev/null +++ b/applications/plugins/protoview/protocols/oregon2.txt @@ -0,0 +1,6 @@ +11001100110011001100110011001100110011001100110011001100110 (Preamble) +10 01 01 10 10 01 01 10 (Sync) +01 10 10 01 10 01 10 01 01 10 10 01 01 10 01 10 10 01 01 10 10 01 10 01 10 01 10 01 10 01 10 01 01 10 10 01 10 01 10 01 01 10 01 10 01 10 01 10 01 10 01 10 10 01 01 10 01 10 10 01 10 01 10 01 10 01 10 01 01 10 10 01 10 01 01 10 01 10 10 01 01 10 10 01 10 01 10 01 10 01 10 01 10 01 11 0 + +We need to seek the following bytes: 01100110 01100110 10010110 10010110 + 0x66 0x66 96 96 diff --git a/applications/plugins/protoview/protocols/renault_tpms.c b/applications/plugins/protoview/protocols/renault_tpms.c new file mode 100644 index 000000000..3022a5d4e --- /dev/null +++ b/applications/plugins/protoview/protocols/renault_tpms.c @@ -0,0 +1,63 @@ +/* Renault tires TPMS. Usually 443.92 Mhz FSK. + * + * Preamble + marshal-encoded bits. 9 Bytes in total if we don't + * count the preamble. */ + +#include "../app.h" + +#define USE_TEST_VECTOR 0 +static const char *test_vector = + "10101010" "10101010" "10101010" "10101001" // Preamble + sync. + + /* The following is marshal encoded, so each two characters are + * actaully one bit. 01 = 1, 10 = 0. */ + "010110010110" // Flags. + "10011001101010011001" // Pressure, multiply by 0.75 to obtain kpa. + // 244 kpa here. + "1010010110011010" // Temperature, subtract 30 to obtain celsius. 22C here. + "1001010101101001" + "0101100110010101" + "1001010101100110" // Tire ID. 0x7AD779 here. + "0101010101010101" + "0101010101010101" // Two FF bytes (usually). Unknown. + "0110010101010101"; // CRC8 with (poly 7, initialization 0). + +static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) { + + if (USE_TEST_VECTOR) { /* Test vector to check that decoding works. */ + bitmap_set_pattern(bits,numbytes,test_vector); + numbits = strlen(test_vector); + } + + if (numbits < 13*8) return false; + + const char *sync_pattern = "10101010" "10101010" "10101010" "10101001"; + uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern); + if (off == BITMAP_SEEK_NOT_FOUND) return false; + FURI_LOG_E(TAG, "Renault TPMS preamble+sync found"); + + off += 32; /* Skip preamble. */ + + uint8_t raw[9]; + uint32_t decoded = + convert_from_line_code(raw,sizeof(raw),bits,numbytes,off, + "10","01"); /* Manchester. */ + FURI_LOG_E(TAG, "Renault TPMS decoded bits: %lu", decoded); + + if (decoded < 8*9) return false; /* Require the full 9 bytes. */ + + float kpa = 0.75 *((uint32_t)((raw[0]&3)<<8) | raw[1]); + int temp = raw[2]-30; + + snprintf(info->name,sizeof(info->name),"%s","Renault TPMS"); + snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X", + raw[0],raw[1],raw[2],raw[3],raw[4],raw[5], + raw[6],raw[7],raw[8]); + snprintf(info->info1,sizeof(info->info1),"Pressure %.2f kpa", (double)kpa); + snprintf(info->info2,sizeof(info->info2),"Temperature %d C", temp); + return true; +} + +ProtoViewDecoder RenaultTPMSDecoder = { + "Renault TPMS", decode +}; diff --git a/applications/plugins/protoview/signal.c b/applications/plugins/protoview/signal.c new file mode 100644 index 000000000..2ff632811 --- /dev/null +++ b/applications/plugins/protoview/signal.c @@ -0,0 +1,420 @@ +/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved + * See the LICENSE file for information about the license. */ + +#include "app.h" + +bool decode_signal(RawSamplesBuffer *s, uint64_t len, ProtoViewMsgInfo *info); +void initialize_msg_info(ProtoViewMsgInfo *i); + +/* ============================================================================= + * Raw signal detection + * ===========================================================================*/ + +/* Return the time difference between a and b, always >= 0 since + * the absolute value is returned. */ +uint32_t duration_delta(uint32_t a, uint32_t b) { + return a > b ? a - b : b - a; +} + +/* Reset the current signal, so that a new one can be detected. */ +void reset_current_signal(ProtoViewApp *app) { + app->signal_bestlen = 0; + app->signal_offset = 0; + app->signal_decoded = false; + raw_samples_reset(DetectedSamples); + raw_samples_reset(RawSamples); +} + +/* This function starts scanning samples at offset idx looking for the + * longest run of pulses, either high or low, that are not much different + * from each other, for a maximum of three duration classes. + * So for instance 50 successive pulses that are roughly long 340us or 670us + * will be sensed as a coherent signal (example: 312, 361, 700, 334, 667, ...) + * + * The classes are counted separtely for high and low signals (RF on / off) + * because many devices tend to have different pulse lenghts depending on + * the level of the pulse. + * + * For instance Oregon2 sensors, in the case of protocol 2.1 will send + * pulses of ~400us (RF on) VS ~580us (RF off). */ +#define SEARCH_CLASSES 3 +uint32_t search_coherent_signal(RawSamplesBuffer *s, uint32_t idx) { + struct { + uint32_t dur[2]; /* dur[0] = low, dur[1] = high */ + uint32_t count[2]; /* Associated observed frequency. */ + } classes[SEARCH_CLASSES]; + + memset(classes,0,sizeof(classes)); + uint32_t minlen = 30, maxlen = 4000; /* Depends on data rate, here we + allow for high and low. */ + uint32_t len = 0; /* Observed len of coherent samples. */ + s->short_pulse_dur = 0; + for (uint32_t j = idx; j < idx+500; j++) { + bool level; + uint32_t dur; + raw_samples_get(s, j, &level, &dur); + if (dur < minlen || dur > maxlen) break; /* return. */ + + /* Let's see if it matches a class we already have or if we + * can populate a new (yet empty) class. */ + uint32_t k; + for (k = 0; k < SEARCH_CLASSES; k++) { + if (classes[k].count[level] == 0) { + classes[k].dur[level] = dur; + classes[k].count[level] = 1; + break; /* Sample accepted. */ + } else { + uint32_t classavg = classes[k].dur[level]; + uint32_t count = classes[k].count[level]; + uint32_t delta = duration_delta(dur,classavg); + /* Is the difference in duration between this signal and + * the class we are inspecting less than a given percentage? + * If so, accept this signal. */ + if (delta < classavg/8) { /* 100%/8 = 12%. */ + /* It is useful to compute the average of the class + * we are observing. We know how many samples we got so + * far, so we can recompute the average easily. + * By always having a better estimate of the pulse len + * we can avoid missing next samples in case the first + * observed samples are too off. */ + classavg = ((classavg * count) + dur) / (count+1); + classes[k].dur[level] = classavg; + classes[k].count[level]++; + break; /* Sample accepted. */ + } + } + } + + if (k == SEARCH_CLASSES) break; /* No match, return. */ + + /* If we are here, we accepted this sample. Try with the next + * one. */ + len++; + } + + /* Update the buffer setting the shortest pulse we found + * among the three classes. This will be used when scaling + * for visualization. */ + uint32_t short_dur[2] = {0,0}; + for (int j = 0; j < SEARCH_CLASSES; j++) { + for (int level = 0; level < 2; level++) { + if (classes[j].dur[level] == 0) continue; + if (classes[j].count[level] < 3) continue; + if (short_dur[level] == 0 || + short_dur[level] > classes[j].dur[level]) + { + short_dur[level] = classes[j].dur[level]; + } + } + } + + /* Use the average between high and low short pulses duration. + * Often they are a bit different, and using the average is more robust + * when we do decoding sampling at short_pulse_dur intervals. */ + if (short_dur[0] == 0) short_dur[0] = short_dur[1]; + if (short_dur[1] == 0) short_dur[1] = short_dur[0]; + s->short_pulse_dur = (short_dur[0]+short_dur[1])/2; + + return len; +} + +/* Search the buffer with the stored signal (last N samples received) + * in order to find a coherent signal. If a signal that does not appear to + * be just noise is found, it is set in DetectedSamples global signal + * buffer, that is what is rendered on the screen. */ +void scan_for_signal(ProtoViewApp *app) { + /* We need to work on a copy: the RawSamples buffer is populated + * by the background thread receiving data. */ + RawSamplesBuffer *copy = raw_samples_alloc(); + raw_samples_copy(copy,RawSamples); + + /* Try to seek on data that looks to have a regular high low high low + * pattern. */ + uint32_t minlen = 13; /* Min run of coherent samples. Up to + 12 samples it's very easy to mistake + noise for signal. */ + + ProtoViewMsgInfo *info = malloc(sizeof(ProtoViewMsgInfo)); + uint32_t i = 0; + + while (i < copy->total-1) { + uint32_t thislen = search_coherent_signal(copy,i); + + /* For messages that are long enough, attempt decoding. */ + if (thislen > minlen) { + initialize_msg_info(info); + uint32_t saved_idx = copy->idx; /* Save index, see later. */ + /* decode_signal() expects the detected signal to start + * from index .*/ + raw_samples_center(copy,i); + bool decoded = decode_signal(copy,thislen,info); + copy->idx = saved_idx; /* Restore the index as we are scanning + the signal in the loop. */ + + /* Accept this signal as the new signal if either it's longer + * than the previous one, or the previous one was unknown and + * this is decoded. */ + if (thislen > app->signal_bestlen || + (app->signal_decoded == false && decoded)) + { + app->signal_info = *info; + app->signal_bestlen = thislen; + app->signal_decoded = decoded; + raw_samples_copy(DetectedSamples,copy); + raw_samples_center(DetectedSamples,i); + FURI_LOG_E(TAG, "Displayed sample updated (%d samples %lu us)", + (int)thislen, DetectedSamples->short_pulse_dur); + } + } + i += thislen ? thislen : 1; + } + raw_samples_free(copy); + free(info); +} + +/* ============================================================================= + * Decoding + * + * The following code will translates the raw singals as received by + * the CC1101 into logical signals: a bitmap of 0s and 1s sampled at + * the detected data clock interval. + * + * Then the converted signal is passed to the protocols decoders, that look + * for protocol-specific information. We stop at the first decoder that is + * able to decode the data, so protocols here should be registered in + * order of complexity and specificity, with the generic ones at the end. + * ===========================================================================*/ + +/* Set the 'bitpos' bit to value 'val', in the specified bitmap + * 'b' of len 'blen'. + * Out of range bits will silently be discarded. */ +void bitmap_set(uint8_t *b, uint32_t blen, uint32_t bitpos, bool val) { + uint32_t byte = bitpos/8; + uint32_t bit = 7-(bitpos&7); + if (byte >= blen) return; + if (val) + b[byte] |= 1<= blen) return 0; + return (b[byte] & (1< rate/2) numbits++; /* There is another one. */ + + /* Limit how much a single sample can spawn. There are likely no + * protocols doing such long pulses when the rate is low. */ + if (numbits > 1024) numbits = 1024; + + if (0) /* Super verbose, so not under the DEBUG_MSG define. */ + FURI_LOG_E(TAG, "%lu converted into %lu (%d) bits", + dur,numbits,(int)level); + + /* If the signal is too short, let's claim it an interference + * and ignore it completely. */ + if (numbits == 0) continue; + + while(numbits--) bitmap_set(b,blen,bitpos++,level); + } + return bitpos; +} + +/* This function converts the line code used to the final data representation. + * The representation is put inside 'buf', for up to 'buflen' bytes of total + * data. For instance in order to convert manchester I can use "10" and "01" + * as zero and one patterns. It is possible to use "?" inside patterns in + * order to skip certain bits. For instance certain devices encode data twice, + * with each bit encoded in manchester encoding and then in its reversed + * representation. In such a case I could use "10??" and "01??". + * + * The function returns the number of bits converted. It will stop as soon + * as it finds a pattern that does not match zero or one patterns, or when + * the end of the bitmap pointed by 'bits' is reached (the length is + * specified in bytes by the caller, via the 'len' parameters). + * + * The decoding starts at the specified offset (in bits) 'off'. */ +uint32_t convert_from_line_code(uint8_t *buf, uint64_t buflen, uint8_t *bits, uint32_t len, uint32_t off, const char *zero_pattern, const char *one_pattern) +{ + uint32_t decoded = 0; /* Number of bits extracted. */ + len *= 8; /* Convert bytes to bits. */ + while(off < len) { + bool bitval; + if (bitmap_match_bits(bits,len,off,zero_pattern)) { + bitval = false; + off += strlen(zero_pattern); + } else if (bitmap_match_bits(bits,len,off,one_pattern)) { + bitval = true; + off += strlen(one_pattern); + } else { + break; + } + bitmap_set(buf,buflen,decoded++,bitval); + if (decoded/8 == buflen) break; /* No space left on target buffer. */ + } + return decoded; +} + +/* Supported protocols go here, with the relevant implementation inside + * protocols/.c */ + +extern ProtoViewDecoder Oregon2Decoder; +extern ProtoViewDecoder B4B1Decoder; +extern ProtoViewDecoder RenaultTPMSDecoder; + +ProtoViewDecoder *Decoders[] = { + &Oregon2Decoder, /* Oregon sensors v2.1 protocol. */ + &B4B1Decoder, /* PT, SC, ... 24 bits remotes. */ + &RenaultTPMSDecoder, /* Renault TPMS. */ + NULL +}; + +/* Reset the message info structure before passing it to the decoding + * functions. */ +void initialize_msg_info(ProtoViewMsgInfo *i) { + memset(i,0,sizeof(ProtoViewMsgInfo)); +} + +/* This function is called when a new signal is detected. It converts it + * to a bitstream, and the calls the protocol specific functions for + * decoding. If the signal was decoded correctly by some protocol, true + * is returned. Otherwise false is returned. */ +bool decode_signal(RawSamplesBuffer *s, uint64_t len, ProtoViewMsgInfo *info) { + uint32_t bitmap_bits_size = 4096*8; + uint32_t bitmap_size = bitmap_bits_size/8; + + /* We call the decoders with an offset a few bits before the actual + * signal detected and for a len of a few bits after its end. */ + uint32_t before_after_bits = 2; + + uint8_t *bitmap = malloc(bitmap_size); + uint32_t bits = convert_signal_to_bits(bitmap,bitmap_size,s,-before_after_bits,len+before_after_bits*2,s->short_pulse_dur); + + if (DEBUG_MSG) { /* Useful for debugging purposes. Don't remove. */ + char *str = malloc(1024); + uint32_t j; + for (j = 0; j < bits && j < 1023; j++) { + str[j] = bitmap_get(bitmap,bitmap_size,j) ? '1' : '0'; + } + str[j] = 0; + FURI_LOG_E(TAG, "%lu bits sampled: %s", bits, str); + free(str); + } + + /* Try all the decoders available. */ + int j = 0; + + bool decoded = false; + while(Decoders[j]) { + uint32_t start_time = furi_get_tick(); + decoded = Decoders[j]->decode(bitmap,bitmap_size,bits,info); + uint32_t delta = furi_get_tick() - start_time; + FURI_LOG_E(TAG, "Decoder %s took %lu ms", + Decoders[j]->name, (unsigned long)delta); + if (decoded) break; + j++; + } + + if (!decoded) { + FURI_LOG_E(TAG, "No decoding possible"); + } else { + FURI_LOG_E(TAG, "Decoded %s, raw=%s info=[%s,%s,%s]", info->name, info->raw, info->info1, info->info2, info->info3); + } + free(bitmap); + return decoded; +} diff --git a/applications/plugins/protoview/ui.c b/applications/plugins/protoview/ui.c new file mode 100644 index 000000000..e22e4d57e --- /dev/null +++ b/applications/plugins/protoview/ui.c @@ -0,0 +1,30 @@ +/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved + * See the LICENSE file for information about the license. */ + +#include "app.h" + +void canvas_draw_str_with_border(Canvas* canvas, uint8_t x, uint8_t y, const char* str, Color text_color, Color border_color) +{ + struct { + uint8_t x; uint8_t y; + } dir[8] = { + {-1,-1}, + {0,-1}, + {1,-1}, + {1,0}, + {1,1}, + {0,1}, + {-1,1}, + {-1,0} + }; + + /* Rotate in all the directions writing the same string to create a + * border, then write the actual string in the other color in the + * middle. */ + canvas_set_color(canvas, border_color); + for (int j = 0; j < 8; j++) + canvas_draw_str(canvas,x+dir[j].x,y+dir[j].y,str); + canvas_set_color(canvas, text_color); + canvas_draw_str(canvas,x,y,str); + canvas_set_color(canvas, ColorBlack); +} diff --git a/applications/plugins/protoview/view_direct_sampling.c b/applications/plugins/protoview/view_direct_sampling.c new file mode 100644 index 000000000..a287dc14a --- /dev/null +++ b/applications/plugins/protoview/view_direct_sampling.c @@ -0,0 +1,46 @@ +/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved + * See the LICENSE file for information about the license. */ + +#include "app.h" + +#include + +/* Read directly from the G0 CC1101 pin, and draw a black or white + * dot depending on the level. */ +void render_view_direct_sampling(Canvas *const canvas, ProtoViewApp *app) { + UNUSED(app); + for (int y = 0; y < 64; y++) { + for (int x = 0; x < 128; x++) { + bool level = furi_hal_gpio_read(&gpio_cc1101_g0); + if (level) canvas_draw_dot(canvas,x,y); + /* Busy loop: this is a terrible approach as it blocks + * everything else, but for now it's the best we can do + * to obtain direct data with some spacing. */ + uint32_t x = 500; while(x--); + } + } + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_with_border(canvas,40,60,"Direct sampling", + ColorWhite,ColorBlack); +} + +/* Handle input */ +void process_input_direct_sampling(ProtoViewApp *app, InputEvent input) { + UNUSED(app); + UNUSED(input); +} + +/* Enter view. Stop the subghz thread to prevent access as we read + * the CC1101 data directly. */ +void view_enter_direct_sampling(ProtoViewApp *app) { + if (app->txrx->txrx_state == TxRxStateRx) { + subghz_worker_stop(app->txrx->worker); + } +} + +/* Exit view. Restore the subghz thread. */ +void view_exit_direct_sampling(ProtoViewApp *app) { + if (app->txrx->txrx_state == TxRxStateRx) { + subghz_worker_start(app->txrx->worker); + } +} diff --git a/applications/plugins/protoview/view_info.c b/applications/plugins/protoview/view_info.c new file mode 100644 index 000000000..fff9c836e --- /dev/null +++ b/applications/plugins/protoview/view_info.c @@ -0,0 +1,41 @@ +/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved + * See the LICENSE file for information about the license. */ + +#include "app.h" + +/* Renders the view with the detected message information. */ +void render_view_info(Canvas *const canvas, ProtoViewApp *app) { + if (app->signal_decoded == false) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas, 30,36,"No signal decoded"); + return; + } + + /* Protocol name as title. */ + canvas_set_font(canvas, FontPrimary); + uint8_t y = 8, lineheight = 10; + canvas_draw_str(canvas, 0, y, app->signal_info.name); + y += lineheight; + + /* Info fields. */ + char buf[128]; + canvas_set_font(canvas, FontSecondary); + if (app->signal_info.raw[0]) { + snprintf(buf,sizeof(buf),"Raw: %s", app->signal_info.raw); + canvas_draw_str(canvas, 0, y, buf); + y += lineheight; + } + canvas_draw_str(canvas, 0, y, app->signal_info.info1); + y += lineheight; + canvas_draw_str(canvas, 0, y, app->signal_info.info2); + y += lineheight; + canvas_draw_str(canvas, 0, y, app->signal_info.info3); + y += lineheight; +} + +/* Handle input for the settings view. */ +void process_input_info(ProtoViewApp *app, InputEvent input) { + UNUSED(app); + UNUSED(input); + return; +} diff --git a/applications/plugins/protoview/view_raw_signal.c b/applications/plugins/protoview/view_raw_signal.c new file mode 100644 index 000000000..58d23e8ee --- /dev/null +++ b/applications/plugins/protoview/view_raw_signal.c @@ -0,0 +1,97 @@ +/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved + * See the LICENSE file for information about the license. */ + +#include "app.h" + +/* Render the received signal. + * + * The screen of the flipper is 128 x 64. Even using 4 pixels per line + * (where low level signal is one pixel high, high level is 4 pixels + * high) and 4 pixels of spacing between the different lines, we can + * plot comfortably 8 lines. + * + * The 'idx' argument is the first sample to render in the circular + * buffer. */ +void render_signal(ProtoViewApp *app, Canvas *const canvas, RawSamplesBuffer *buf, uint32_t idx) { + canvas_set_color(canvas, ColorBlack); + + int rows = 8; + uint32_t time_per_pixel = app->us_scale; + uint32_t start_idx = idx; + bool level = 0; + uint32_t dur = 0, sample_num = 0; + for (int row = 0; row < rows ; row++) { + for (int x = 0; x < 128; x++) { + int y = 3 + row*8; + if (dur < time_per_pixel/2) { + /* Get more data. */ + raw_samples_get(buf, idx++, &level, &dur); + sample_num++; + } + + canvas_draw_line(canvas, x,y,x,y-(level*3)); + + /* Write a small triangle under the last sample detected. */ + if (app->signal_bestlen != 0 && + sample_num+start_idx == app->signal_bestlen+1) + { + canvas_draw_dot(canvas,x,y+2); + canvas_draw_dot(canvas,x-1,y+3); + canvas_draw_dot(canvas,x,y+3); + canvas_draw_dot(canvas,x+1,y+3); + sample_num++; /* Make sure we don't mark the next, too. */ + } + + /* Remove from the current level duration the time we + * just plot. */ + if (dur > time_per_pixel) + dur -= time_per_pixel; + else + dur = 0; + } + } +} + +/* Raw pulses rendering. This is our default view. */ +void render_view_raw_pulses(Canvas *const canvas, ProtoViewApp *app) { + /* Show signal. */ + render_signal(app, canvas, DetectedSamples, app->signal_offset); + + /* Show signal information. */ + char buf[64]; + snprintf(buf,sizeof(buf),"%luus", + (unsigned long)DetectedSamples->short_pulse_dur); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_with_border(canvas, 97, 63, buf, ColorWhite, ColorBlack); + if (app->signal_decoded) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_with_border(canvas, 1, 61, app->signal_info.name, ColorWhite, ColorBlack); + } +} + +/* Handle input for the raw pulses view. */ +void process_input_raw_pulses(ProtoViewApp *app, InputEvent input) { + if (input.type == InputTypeRepeat) { + /* Handle panning of the signal window. Long pressing + * right will show successive samples, long pressing left + * previous samples. */ + if (input.key == InputKeyRight) app->signal_offset++; + else if (input.key == InputKeyLeft) app->signal_offset--; + else if (input.key == InputKeyOk) { + app->signal_offset = 0; + app->us_scale = PROTOVIEW_RAW_VIEW_DEFAULT_SCALE; + } + } else if (input.type == InputTypeShort) { + if (input.key == InputKeyOk) { + /* Reset the current sample to capture the next. */ + reset_current_signal(app); + } else if (input.key == InputKeyDown) { + /* Rescaling. The set becomes finer under 50us per pixel. */ + uint32_t scale_step = app->us_scale >= 50 ? 50 : 10; + if (app->us_scale < 500) app->us_scale += scale_step; + } else if (input.key == InputKeyUp) { + uint32_t scale_step = app->us_scale > 50 ? 50 : 10; + if (app->us_scale > 10) app->us_scale -= scale_step; + } + } +} diff --git a/applications/plugins/protoview/view_settings.c b/applications/plugins/protoview/view_settings.c new file mode 100644 index 000000000..22ac5ef0f --- /dev/null +++ b/applications/plugins/protoview/view_settings.c @@ -0,0 +1,93 @@ +/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved + * See the LICENSE file for information about the license. */ + +#include "app.h" + +/* Renders a single view with frequency and modulation setting. However + * this are logically two different views, and only one of the settings + * will be highlighted. */ +void render_view_settings(Canvas *const canvas, ProtoViewApp *app) { + canvas_set_font(canvas, FontPrimary); + if (app->current_view == ViewFrequencySettings) + canvas_draw_str_with_border(canvas,1,10,"Frequency",ColorWhite,ColorBlack); + else + canvas_draw_str(canvas,1,10,"Frequency"); + + if (app->current_view == ViewModulationSettings) + canvas_draw_str_with_border(canvas,70,10,"Modulation",ColorWhite,ColorBlack); + else + canvas_draw_str(canvas,70,10,"Modulation"); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str(canvas,10,61,"Use up and down to modify"); + + /* Show frequency. We can use big numbers font since it's just a number. */ + if (app->current_view == ViewFrequencySettings) { + char buf[16]; + snprintf(buf,sizeof(buf),"%.2f",(double)app->frequency/1000000); + canvas_set_font(canvas, FontBigNumbers); + canvas_draw_str(canvas, 30, 40, buf); + } else if (app->current_view == ViewModulationSettings) { + int current = app->modulation; + canvas_set_font(canvas, FontPrimary); + canvas_draw_str(canvas, 33, 39, ProtoViewModulations[current].name); + } +} + +/* Handle input for the settings view. */ +void process_input_settings(ProtoViewApp *app, InputEvent input) { + if (input.type == InputTypeLong && input.key == InputKeyOk) { + /* Long pressing to OK sets the default frequency and + * modulation. */ + app->frequency = subghz_setting_get_default_frequency(app->setting); + app->modulation = 0; + } else if (input.type == InputTypePress && + (input.key != InputKeyDown || input.key != InputKeyUp)) + { + /* Handle up and down to change frequency or modulation. */ + if (app->current_view == ViewFrequencySettings) { + size_t curidx = 0, i; + size_t count = subghz_setting_get_frequency_count(app->setting); + + /* Scan the list of frequencies to check for the index of the + * currently set frequency. */ + for(i = 0; i < count; i++) { + uint32_t freq = subghz_setting_get_frequency(app->setting,i); + if (freq == app->frequency) { + curidx = i; + break; + } + } + if (i == count) return; /* Should never happen. */ + + if (input.key == InputKeyUp) { + curidx = curidx == 0 ? count-1 : curidx-1; + } else if (input.key == InputKeyDown) { + curidx = (curidx+1) % count; + } else { + return; + } + app->frequency = subghz_setting_get_frequency(app->setting,curidx); + } else if (app->current_view == ViewModulationSettings) { + uint32_t count = 0; + uint32_t modid = app->modulation; + + while(ProtoViewModulations[count].name != NULL) count++; + if (input.key == InputKeyUp) { + modid = modid == 0 ? count-1 : modid-1; + } else if (input.key == InputKeyDown) { + modid = (modid+1) % count; + } else { + return; + } + app->modulation = modid; + } + } else { + return; + } + + /* Apply changes. */ + FURI_LOG_E(TAG, "Setting view, setting frequency/modulation to %lu %s", app->frequency, ProtoViewModulations[app->modulation].name); + radio_rx_end(app); + radio_begin(app); + radio_rx(app); +} diff --git a/applications/plugins/usb_hid_autofire/CHANGELOG.md b/applications/plugins/usb_hid_autofire/CHANGELOG.md index d0924edd3..62929bc8f 100644 --- a/applications/plugins/usb_hid_autofire/CHANGELOG.md +++ b/applications/plugins/usb_hid_autofire/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## 0.5 +- Fix compatibility with Flipper Zero firmware 0.74.2 + ## 0.4 - Show active/inactive state in primary font (bold) diff --git a/applications/plugins/usb_hid_autofire/application.fam b/applications/plugins/usb_hid_autofire/application.fam index 10e3e6cf4..8a184d674 100644 --- a/applications/plugins/usb_hid_autofire/application.fam +++ b/applications/plugins/usb_hid_autofire/application.fam @@ -1,5 +1,7 @@ +# qv. https://github.com/flipperdevices/flipperzero-firmware/blob/dev/documentation/AppManifests.md + App( - appid="USB_HID_Autofire", + appid="usb_hid_autofire", name="USB HID Autofire", apptype=FlipperAppType.EXTERNAL, entry_point="usb_hid_autofire_app", diff --git a/applications/plugins/usb_hid_autofire/tools.c b/applications/plugins/usb_hid_autofire/tools.c new file mode 100644 index 000000000..2b452b55b --- /dev/null +++ b/applications/plugins/usb_hid_autofire/tools.c @@ -0,0 +1,56 @@ +// +// Tools for USB HID Autofire +// + +void strrev(char* arr, int start, int end) { + char temp; + + if (start >= end) + return; + + temp = *(arr + start); + *(arr + start) = *(arr + end); + *(arr + end) = temp; + + start++; + end--; + strrev(arr, start, end); +} + +char *itoa(int number, char *arr, int base) +{ + int i = 0, r, negative = 0; + + if (number == 0) + { + arr[i] = '0'; + arr[i + 1] = '\0'; + return arr; + } + + if (number < 0 && base == 10) + { + number *= -1; + negative = 1; + } + + while (number != 0) + { + r = number % base; + arr[i] = (r > 9) ? (r - 10) + 'a' : r + '0'; + i++; + number /= base; + } + + if (negative) + { + arr[i] = '-'; + i++; + } + + strrev(arr, 0, i - 1); + + arr[i] = '\0'; + + return arr; +} diff --git a/applications/plugins/usb_hid_autofire/tools.h b/applications/plugins/usb_hid_autofire/tools.h new file mode 100644 index 000000000..9c71ea6ca --- /dev/null +++ b/applications/plugins/usb_hid_autofire/tools.h @@ -0,0 +1,7 @@ +#ifndef FLIPPERZERO_FIRMWARE_TOOLS_H +#define FLIPPERZERO_FIRMWARE_TOOLS_H + +void strrev(char *arr, int start, int end); +char *itoa(int number, char *arr, int base); + +#endif //FLIPPERZERO_FIRMWARE_TOOLS_H diff --git a/applications/plugins/usb_hid_autofire/usb_hid_autofire.c b/applications/plugins/usb_hid_autofire/usb_hid_autofire.c index 97c9d4759..1333ba94a 100644 --- a/applications/plugins/usb_hid_autofire/usb_hid_autofire.c +++ b/applications/plugins/usb_hid_autofire/usb_hid_autofire.c @@ -4,6 +4,7 @@ #include #include #include "version.h" +#include "tools.h" // Uncomment to be able to make a screenshot //#define USB_HID_AUTOFIRE_SCREENSHOT @@ -25,7 +26,9 @@ uint32_t autofire_delay = 10; static void usb_hid_autofire_render_callback(Canvas* canvas, void* ctx) { UNUSED(ctx); char autofire_delay_str[12]; + //std::string pi = "pi is " + std::to_string(3.1415926); itoa(autofire_delay, autofire_delay_str, 10); + //sprintf(autofire_delay_str, "%lu", autofire_delay); canvas_clear(canvas); @@ -85,19 +88,19 @@ int32_t usb_hid_autofire_app(void* p) { } switch(event.input.key) { - case InputKeyOk: - btn_left_autofire = !btn_left_autofire; - break; - case InputKeyLeft: - if(autofire_delay > 0) { - autofire_delay -= 10; - } - break; - case InputKeyRight: - autofire_delay += 10; - break; - default: - break; + case InputKeyOk: + btn_left_autofire = !btn_left_autofire; + break; + case InputKeyLeft: + if(autofire_delay > 0) { + autofire_delay -= 10; + } + break; + case InputKeyRight: + autofire_delay += 10; + break; + default: + break; } } } diff --git a/applications/plugins/usb_hid_autofire/version.h b/applications/plugins/usb_hid_autofire/version.h index ac1f5d0fa..669b388a5 100644 --- a/applications/plugins/usb_hid_autofire/version.h +++ b/applications/plugins/usb_hid_autofire/version.h @@ -1 +1 @@ -#define VERSION "0.4" +#define VERSION "0.5"