Update UniTemp and run fbt format

This commit is contained in:
MX 2023-01-18 22:25:39 +03:00
parent e4aad248cf
commit 5a9da13d84
No known key found for this signature in database
GPG key ID: 6C4C311DFD4B4AB5
36 changed files with 1478 additions and 966 deletions

View file

@ -40,8 +40,8 @@ extern const SubGhzProtocolRegistry protoview_protocol_registry;
/* The callback actually just passes the control to the actual active /* The callback actually just passes the control to the actual active
* view callback, after setting up basic stuff like cleaning the screen * view callback, after setting up basic stuff like cleaning the screen
* and setting color to black. */ * and setting color to black. */
static void render_callback(Canvas *const canvas, void *ctx) { static void render_callback(Canvas* const canvas, void* ctx) {
ProtoViewApp *app = ctx; ProtoViewApp* app = ctx;
/* Clear screen. */ /* Clear screen. */
canvas_set_color(canvas, ColorWhite); canvas_set_color(canvas, ColorWhite);
@ -51,62 +51,69 @@ static void render_callback(Canvas *const canvas, void *ctx) {
/* Call who is in charge right now. */ /* Call who is in charge right now. */
switch(app->current_view) { switch(app->current_view) {
case ViewRawPulses: render_view_raw_pulses(canvas,app); break; case ViewRawPulses:
case ViewInfo: render_view_info(canvas,app); break; render_view_raw_pulses(canvas, app);
break;
case ViewInfo:
render_view_info(canvas, app);
break;
case ViewFrequencySettings: case ViewFrequencySettings:
case ViewModulationSettings: case ViewModulationSettings:
render_view_settings(canvas,app); break; render_view_settings(canvas, app);
case ViewDirectSampling: render_view_direct_sampling(canvas,app); break; break;
case ViewLast: furi_crash(TAG " ViewLast selected"); break; case ViewDirectSampling:
render_view_direct_sampling(canvas, app);
break;
case ViewLast:
furi_crash(TAG " ViewLast selected");
break;
} }
} }
/* Here all we do is putting the events into the queue that will be handled /* Here all we do is putting the events into the queue that will be handled
* in the while() loop of the app entry point function. */ * in the while() loop of the app entry point function. */
static void input_callback(InputEvent* input_event, void* ctx) static void input_callback(InputEvent* input_event, void* ctx) {
{ ProtoViewApp* app = ctx;
ProtoViewApp *app = ctx; furi_message_queue_put(app->event_queue, input_event, FuriWaitForever);
furi_message_queue_put(app->event_queue,input_event,FuriWaitForever);
} }
/* Called to switch view (when left/right is pressed). Handles /* Called to switch view (when left/right is pressed). Handles
* changing the current view ID and calling the enter/exit view * changing the current view ID and calling the enter/exit view
* callbacks if needed. */ * callbacks if needed. */
static void app_switch_view(ProtoViewApp *app, SwitchViewDirection dir) { static void app_switch_view(ProtoViewApp* app, SwitchViewDirection dir) {
ProtoViewCurrentView old = app->current_view; ProtoViewCurrentView old = app->current_view;
if (dir == AppNextView) { if(dir == AppNextView) {
app->current_view++; app->current_view++;
if (app->current_view == ViewLast) app->current_view = 0; if(app->current_view == ViewLast) app->current_view = 0;
} else if (dir == AppPrevView) { } else if(dir == AppPrevView) {
if (app->current_view == 0) if(app->current_view == 0)
app->current_view = ViewLast-1; app->current_view = ViewLast - 1;
else else
app->current_view--; app->current_view--;
} }
ProtoViewCurrentView new = app->current_view; ProtoViewCurrentView new = app->current_view;
/* Call the enter/exit view callbacks if needed. */ /* Call the enter/exit view callbacks if needed. */
if (old == ViewDirectSampling) view_exit_direct_sampling(app); if(old == ViewDirectSampling) view_exit_direct_sampling(app);
if (new == ViewDirectSampling) view_enter_direct_sampling(app); if(new == ViewDirectSampling) view_enter_direct_sampling(app);
/* The frequency/modulation settings are actually a single view: /* The frequency/modulation settings are actually a single view:
* as long as the user stays between the two modes of this view we * as long as the user stays between the two modes of this view we
* don't need to call the exit-view callback. */ * don't need to call the exit-view callback. */
if ((old == ViewFrequencySettings && new != ViewModulationSettings) || if((old == ViewFrequencySettings && new != ViewModulationSettings) ||
(old == ViewModulationSettings && new != ViewFrequencySettings)) (old == ViewModulationSettings && new != ViewFrequencySettings))
view_exit_settings(app); view_exit_settings(app);
/* Set the current subview of the view we just left to zero, that is /* Set the current subview of the view we just left to zero, that is
* the main subview of the view. When re re-enter it we want to see * the main subview of the view. When re re-enter it we want to see
* the main thing. */ * the main thing. */
app->current_subview[old] = 0; app->current_subview[old] = 0;
memset(app->view_privdata,0,PROTOVIEW_VIEW_PRIVDATA_LEN); memset(app->view_privdata, 0, PROTOVIEW_VIEW_PRIVDATA_LEN);
} }
/* Allocate the application state and initialize a number of stuff. /* Allocate the application state and initialize a number of stuff.
* This is called in the entry point to create the application state. */ * This is called in the entry point to create the application state. */
ProtoViewApp* protoview_app_alloc() { ProtoViewApp* protoview_app_alloc() {
ProtoViewApp *app = malloc(sizeof(ProtoViewApp)); ProtoViewApp* app = malloc(sizeof(ProtoViewApp));
// Init shared data structures // Init shared data structures
RawSamples = raw_samples_alloc(); RawSamples = raw_samples_alloc();
@ -127,10 +134,10 @@ ProtoViewApp* protoview_app_alloc() {
app->text_input = NULL; app->text_input = NULL;
app->show_text_input = false; app->show_text_input = false;
app->current_view = ViewRawPulses; app->current_view = ViewRawPulses;
for (int j = 0; j < ViewLast; j++) app->current_subview[j] = 0; for(int j = 0; j < ViewLast; j++) app->current_subview[j] = 0;
app->direct_sampling_enabled = false; app->direct_sampling_enabled = false;
app->view_privdata = malloc(PROTOVIEW_VIEW_PRIVDATA_LEN); app->view_privdata = malloc(PROTOVIEW_VIEW_PRIVDATA_LEN);
memset(app->view_privdata,0,PROTOVIEW_VIEW_PRIVDATA_LEN); memset(app->view_privdata, 0, PROTOVIEW_VIEW_PRIVDATA_LEN);
// Signal found and visualization defaults // Signal found and visualization defaults
app->signal_bestlen = 0; app->signal_bestlen = 0;
@ -155,17 +162,14 @@ ProtoViewApp* protoview_app_alloc() {
app->txrx->environment = subghz_environment_alloc(); app->txrx->environment = subghz_environment_alloc();
subghz_environment_set_protocol_registry( subghz_environment_set_protocol_registry(
app->txrx->environment, (void*)&protoview_protocol_registry); app->txrx->environment, (void*)&protoview_protocol_registry);
app->txrx->receiver = app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment);
subghz_receiver_alloc_init(app->txrx->environment); subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable);
subghz_receiver_set_filter(app->txrx->receiver,
SubGhzProtocolFlag_Decodable);
subghz_worker_set_overrun_callback( subghz_worker_set_overrun_callback(
app->txrx->worker, app->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset);
(SubGhzWorkerOverrunCallback)subghz_receiver_reset);
subghz_worker_set_pair_callback( subghz_worker_set_pair_callback(
app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
subghz_worker_set_context(app->txrx->worker, app->txrx->receiver); subghz_worker_set_context(app->txrx->worker, app->txrx->receiver);
app->frequency = subghz_setting_get_default_frequency(app->setting); app->frequency = subghz_setting_get_default_frequency(app->setting);
app->modulation = 0; /* Defaults to ProtoViewModulations[0]. */ app->modulation = 0; /* Defaults to ProtoViewModulations[0]. */
@ -178,7 +182,7 @@ ProtoViewApp* protoview_app_alloc() {
/* Free what the application allocated. It is not clear to me if the /* Free what the application allocated. It is not clear to me if the
* Flipper OS, once the application exits, will be able to reclaim space * Flipper OS, once the application exits, will be able to reclaim space
* even if we forget to free something here. */ * even if we forget to free something here. */
void protoview_app_free(ProtoViewApp *app) { void protoview_app_free(ProtoViewApp* app) {
furi_assert(app); furi_assert(app);
// Put CC1101 on sleep. // Put CC1101 on sleep.
@ -196,7 +200,7 @@ void protoview_app_free(ProtoViewApp *app) {
subghz_setting_free(app->setting); subghz_setting_free(app->setting);
// Worker stuff. // Worker stuff.
if (!app->txrx->debug_timer_sampling) { if(!app->txrx->debug_timer_sampling) {
subghz_receiver_free(app->txrx->receiver); subghz_receiver_free(app->txrx->receiver);
subghz_environment_free(app->txrx->environment); subghz_environment_free(app->txrx->environment);
subghz_worker_free(app->txrx->worker); subghz_worker_free(app->txrx->worker);
@ -214,8 +218,8 @@ void protoview_app_free(ProtoViewApp *app) {
/* Called periodically. Do signal processing here. Data we process here /* Called periodically. Do signal processing here. Data we process here
* will be later displayed by the render callback. The side effect of this * will be later displayed by the render callback. The side effect of this
* function is to scan for signals and set DetectedSamples. */ * function is to scan for signals and set DetectedSamples. */
static void timer_callback(void *ctx) { static void timer_callback(void* ctx) {
ProtoViewApp *app = ctx; ProtoViewApp* app = ctx;
uint32_t delta, lastidx = app->signal_last_scan_idx; uint32_t delta, lastidx = app->signal_last_scan_idx;
/* scan_for_signal(), called by this function, deals with a /* scan_for_signal(), called by this function, deals with a
@ -223,22 +227,22 @@ static void timer_callback(void *ctx) {
* cross-boundaries, it is enough if we scan each time the buffer fills * cross-boundaries, it is enough if we scan each time the buffer fills
* for 50% more compared to the last scan. Thanks to this check we * for 50% more compared to the last scan. Thanks to this check we
* can avoid scanning too many times to just find the same data. */ * can avoid scanning too many times to just find the same data. */
if (lastidx < RawSamples->idx) { if(lastidx < RawSamples->idx) {
delta = RawSamples->idx - lastidx; delta = RawSamples->idx - lastidx;
} else { } else {
delta = RawSamples->total - lastidx + RawSamples->idx; delta = RawSamples->total - lastidx + RawSamples->idx;
} }
if (delta < RawSamples->total/2) return; if(delta < RawSamples->total / 2) return;
app->signal_last_scan_idx = RawSamples->idx; app->signal_last_scan_idx = RawSamples->idx;
scan_for_signal(app); scan_for_signal(app);
} }
int32_t protoview_app_entry(void* p) { int32_t protoview_app_entry(void* p) {
UNUSED(p); UNUSED(p);
ProtoViewApp *app = protoview_app_alloc(); ProtoViewApp* app = protoview_app_alloc();
/* Create a timer. We do data analysis in the callback. */ /* Create a timer. We do data analysis in the callback. */
FuriTimer *timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, app); FuriTimer* timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, app);
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8); furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8);
/* Start listening to signals immediately. */ /* Start listening to signals immediately. */
@ -253,58 +257,57 @@ int32_t protoview_app_entry(void* p) {
InputEvent input; InputEvent input;
while(app->running) { while(app->running) {
FuriStatus qstat = furi_message_queue_get(app->event_queue, &input, 100); FuriStatus qstat = furi_message_queue_get(app->event_queue, &input, 100);
if (qstat == FuriStatusOk) { if(qstat == FuriStatusOk) {
if (DEBUG_MSG) FURI_LOG_E(TAG, "Main Loop - Input: type %d key %u", if(DEBUG_MSG)
input.type, input.key); FURI_LOG_E(TAG, "Main Loop - Input: type %d key %u", input.type, input.key);
/* Handle navigation here. Then handle view-specific inputs /* Handle navigation here. Then handle view-specific inputs
* in the view specific handling function. */ * in the view specific handling function. */
if (input.type == InputTypeShort && if(input.type == InputTypeShort && input.key == InputKeyBack) {
input.key == InputKeyBack)
{
/* Exit the app. */ /* Exit the app. */
app->running = 0; app->running = 0;
} else if (input.type == InputTypeShort && } else if(
input.key == InputKeyRight && input.type == InputTypeShort && input.key == InputKeyRight &&
get_current_subview(app) == 0) get_current_subview(app) == 0) {
{
/* Go to the next view. */ /* Go to the next view. */
app_switch_view(app,AppNextView); app_switch_view(app, AppNextView);
} else if (input.type == InputTypeShort && } else if(
input.key == InputKeyLeft && input.type == InputTypeShort && input.key == InputKeyLeft &&
get_current_subview(app) == 0) get_current_subview(app) == 0) {
{
/* Go to the previous view. */ /* Go to the previous view. */
app_switch_view(app,AppPrevView); app_switch_view(app, AppPrevView);
} else { } else {
/* This is where we pass the control to the currently /* This is where we pass the control to the currently
* active view input processing. */ * active view input processing. */
switch(app->current_view) { switch(app->current_view) {
case ViewRawPulses: case ViewRawPulses:
process_input_raw_pulses(app,input); process_input_raw_pulses(app, input);
break; break;
case ViewInfo: case ViewInfo:
process_input_info(app,input); process_input_info(app, input);
break; break;
case ViewFrequencySettings: case ViewFrequencySettings:
case ViewModulationSettings: case ViewModulationSettings:
process_input_settings(app,input); process_input_settings(app, input);
break; break;
case ViewDirectSampling: case ViewDirectSampling:
process_input_direct_sampling(app,input); process_input_direct_sampling(app, input);
break;
case ViewLast:
furi_crash(TAG " ViewLast selected");
break; break;
case ViewLast: furi_crash(TAG " ViewLast selected"); break;
} }
} }
} else { } else {
/* Useful to understand if the app is still alive when it /* Useful to understand if the app is still alive when it
* does not respond because of bugs. */ * does not respond because of bugs. */
if (DEBUG_MSG) { if(DEBUG_MSG) {
static int c = 0; c++; static int c = 0;
if (!(c % 20)) FURI_LOG_E(TAG, "Loop timeout"); c++;
if(!(c % 20)) FURI_LOG_E(TAG, "Loop timeout");
} }
} }
if (app->show_text_input) { if(app->show_text_input) {
/* Remove our viewport: we need to use a view dispatcher /* Remove our viewport: we need to use a view dispatcher
* in order to show the standard Flipper keyboard. */ * in order to show the standard Flipper keyboard. */
gui_remove_view_port(app->gui, app->view_port); gui_remove_view_port(app->gui, app->view_port);
@ -314,8 +317,9 @@ int32_t protoview_app_entry(void* p) {
app->view_dispatcher = view_dispatcher_alloc(); app->view_dispatcher = view_dispatcher_alloc();
view_dispatcher_enable_queue(app->view_dispatcher); view_dispatcher_enable_queue(app->view_dispatcher);
app->text_input = text_input_alloc(); app->text_input = text_input_alloc();
view_dispatcher_set_event_callback_context(app->view_dispatcher,app); view_dispatcher_set_event_callback_context(app->view_dispatcher, app);
view_dispatcher_add_view(app->view_dispatcher, 0, text_input_get_view(app->text_input)); view_dispatcher_add_view(
app->view_dispatcher, 0, text_input_get_view(app->text_input));
view_dispatcher_switch_to_view(app->view_dispatcher, 0); view_dispatcher_switch_to_view(app->view_dispatcher, 0);
/* Setup the text input view. The different parameters are set /* Setup the text input view. The different parameters are set
@ -331,7 +335,8 @@ int32_t protoview_app_entry(void* p) {
false); false);
/* Run the dispatcher with the keyboard. */ /* Run the dispatcher with the keyboard. */
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); view_dispatcher_attach_to_gui(
app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
view_dispatcher_run(app->view_dispatcher); view_dispatcher_run(app->view_dispatcher);
/* Undo all it: remove the view from the dispatcher, free it /* Undo all it: remove the view from the dispatcher, free it
@ -349,7 +354,7 @@ int32_t protoview_app_entry(void* p) {
} }
/* App no longer running. Shut down and free. */ /* App no longer running. Shut down and free. */
if (app->txrx->txrx_state == TxRxStateRx) { if(app->txrx->txrx_state == TxRxStateRx) {
FURI_LOG_E(TAG, "Putting CC1101 to sleep before exiting."); FURI_LOG_E(TAG, "Putting CC1101 to sleep before exiting.");
radio_rx_end(app); radio_rx_end(app);
radio_sleep(app); radio_sleep(app);
@ -359,4 +364,3 @@ int32_t protoview_app_entry(void* p) {
protoview_app_free(app); protoview_app_free(app);
return 0; return 0;
} }

View file

@ -50,17 +50,14 @@ typedef enum {
} ProtoViewCurrentView; } ProtoViewCurrentView;
/* Used by app_switch_view() */ /* Used by app_switch_view() */
typedef enum { typedef enum { AppNextView, AppPrevView } SwitchViewDirection;
AppNextView,
AppPrevView
} SwitchViewDirection;
typedef struct { typedef struct {
const char *name; // Name to show to the user. const char* name; // Name to show to the user.
const char *id; // Identifier in the Flipper API/file. const char* id; // Identifier in the Flipper API/file.
FuriHalSubGhzPreset preset; // The preset ID. FuriHalSubGhzPreset preset; // The preset ID.
uint8_t *custom; // If not null, a set of registers for uint8_t* custom; // If not null, a set of registers for
// the CC1101, specifying a custom preset. // the CC1101, specifying a custom preset.
} ProtoViewModulation; } ProtoViewModulation;
extern ProtoViewModulation ProtoViewModulations[]; /* In app_subghz.c */ extern ProtoViewModulation ProtoViewModulations[]; /* In app_subghz.c */
@ -69,19 +66,19 @@ extern ProtoViewModulation ProtoViewModulations[]; /* In app_subghz.c */
* It receives data and we get our protocol "feed" callback called * It receives data and we get our protocol "feed" callback called
* with the level (1 or 0) and duration. */ * with the level (1 or 0) and duration. */
struct ProtoViewTxRx { struct ProtoViewTxRx {
bool freq_mod_changed; /* The user changed frequency and/or modulation bool freq_mod_changed; /* The user changed frequency and/or modulation
from the interface. There is to restart the from the interface. There is to restart the
radio with the right parameters. */ radio with the right parameters. */
SubGhzWorker* worker; /* Our background worker. */ SubGhzWorker* worker; /* Our background worker. */
SubGhzEnvironment* environment; SubGhzEnvironment* environment;
SubGhzReceiver* receiver; SubGhzReceiver* receiver;
TxRxState txrx_state; /* Receiving, idle or sleeping? */ TxRxState txrx_state; /* Receiving, idle or sleeping? */
/* Timer sampling mode state. */ /* Timer sampling mode state. */
bool debug_timer_sampling; /* Read data from GDO0 in a busy loop. Only bool debug_timer_sampling; /* Read data from GDO0 in a busy loop. Only
for testing. */ for testing. */
uint32_t last_g0_change_time; /* Last high->low (or reverse) switch. */ uint32_t last_g0_change_time; /* Last high->low (or reverse) switch. */
bool last_g0_value; /* Current value (high or low): we are bool last_g0_value; /* Current value (high or low): we are
checking the duration in the timer checking the duration in the timer
handler. */ handler. */
}; };
@ -103,49 +100,49 @@ typedef struct ProtoViewMsgInfo {
char info4[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 4. */ char info4[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 4. */
/* Low level information of the detected signal: the following are filled /* Low level information of the detected signal: the following are filled
* by the protocol decoding function: */ * by the protocol decoding function: */
uint32_t start_off; /* Pulses start offset in the bitmap. */ uint32_t start_off; /* Pulses start offset in the bitmap. */
uint32_t pulses_count; /* Number of pulses of the full message. */ uint32_t pulses_count; /* Number of pulses of the full message. */
/* The following are passed already filled to the decoder. */ /* The following are passed already filled to the decoder. */
uint32_t short_pulse_dur; /* Microseconds duration of the short pulse. */ uint32_t short_pulse_dur; /* Microseconds duration of the short pulse. */
/* The following are filled by ProtoView core after the decoder returned /* The following are filled by ProtoView core after the decoder returned
* success. */ * success. */
uint8_t *bits; /* Bitmap with the signal. */ uint8_t* bits; /* Bitmap with the signal. */
uint32_t bits_bytes; /* Number of full bytes in the bitmap, that uint32_t bits_bytes; /* Number of full bytes in the bitmap, that
is 'pulses_count/8' rounded to the next is 'pulses_count/8' rounded to the next
integer. */ integer. */
} ProtoViewMsgInfo; } ProtoViewMsgInfo;
struct ProtoViewApp { struct ProtoViewApp {
/* GUI */ /* GUI */
Gui *gui; Gui* gui;
ViewPort *view_port; /* We just use a raw viewport and we render ViewPort* view_port; /* We just use a raw viewport and we render
everything into the low level canvas. */ everything into the low level canvas. */
ProtoViewCurrentView current_view; /* Active left-right view ID. */ ProtoViewCurrentView current_view; /* Active left-right view ID. */
int current_subview[ViewLast]; /* Active up-down subview ID. */ int current_subview[ViewLast]; /* Active up-down subview ID. */
FuriMessageQueue *event_queue; /* Keypress events go here. */ FuriMessageQueue* event_queue; /* Keypress events go here. */
ViewDispatcher *view_dispatcher; /* Used only when we want to show ViewDispatcher* view_dispatcher; /* Used only when we want to show
the text_input view for a moment. the text_input view for a moment.
Otherwise it is set to null. */ Otherwise it is set to null. */
TextInput *text_input; TextInput* text_input;
bool show_text_input; bool show_text_input;
char *text_input_buffer; char* text_input_buffer;
uint32_t text_input_buffer_len; uint32_t text_input_buffer_len;
void (*text_input_done_callback)(void*); void (*text_input_done_callback)(void*);
/* Radio related. */ /* Radio related. */
ProtoViewTxRx *txrx; /* Radio state. */ ProtoViewTxRx* txrx; /* Radio state. */
SubGhzSetting *setting; /* A list of valid frequencies. */ SubGhzSetting* setting; /* A list of valid frequencies. */
/* Generic app state. */ /* Generic app state. */
int running; /* Once false exists the app. */ int running; /* Once false exists the app. */
uint32_t signal_bestlen; /* Longest coherent signal observed so far. */ uint32_t signal_bestlen; /* Longest coherent signal observed so far. */
uint32_t signal_last_scan_idx; /* Index of the buffer last time we uint32_t signal_last_scan_idx; /* Index of the buffer last time we
performed the scan. */ performed the scan. */
bool signal_decoded; /* Was the current signal decoded? */ bool signal_decoded; /* Was the current signal decoded? */
ProtoViewMsgInfo *msg_info; /* Decoded message info if not NULL. */ ProtoViewMsgInfo* msg_info; /* Decoded message info if not NULL. */
bool direct_sampling_enabled; /* This special view needs an explicit bool direct_sampling_enabled; /* This special view needs an explicit
acknowledge to work. */ acknowledge to work. */
void *view_privdata; /* This is a piece of memory of total size void* view_privdata; /* This is a piece of memory of total size
PROTOVIEW_VIEW_PRIVDATA_LEN that it is PROTOVIEW_VIEW_PRIVDATA_LEN that it is
initialized to zero when we switch to initialized to zero when we switch to
a a new view. While the view we are using a a new view. While the view we are using
@ -154,17 +151,17 @@ struct ProtoViewApp {
the pointer to a few specific-data structure. */ the pointer to a few specific-data structure. */
/* Raw view apps state. */ /* Raw view apps state. */
uint32_t us_scale; /* microseconds per pixel. */ uint32_t us_scale; /* microseconds per pixel. */
uint32_t signal_offset; /* Long press left/right panning in raw view. */ uint32_t signal_offset; /* Long press left/right panning in raw view. */
/* Configuration view app state. */ /* Configuration view app state. */
uint32_t frequency; /* Current frequency. */ uint32_t frequency; /* Current frequency. */
uint8_t modulation; /* Current modulation ID, array index in the uint8_t modulation; /* Current modulation ID, array index in the
ProtoViewModulations table. */ ProtoViewModulations table. */
}; };
typedef struct ProtoViewDecoder { typedef struct ProtoViewDecoder {
const char *name; /* Protocol name. */ const char* name; /* Protocol name. */
/* The decode function takes a buffer that is actually a bitmap, with /* 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 * 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, * pulses represented by the bitmap is passed as the 'numbits' argument,
@ -172,7 +169,7 @@ typedef struct ProtoViewDecoder {
* 'bits'. So 'numbytes' is mainly useful to pass as argument to other * 'bits'. So 'numbytes' is mainly useful to pass as argument to other
* functions that perform bit extraction with bound checking, such as * functions that perform bit extraction with bound checking, such as
* bitmap_get() and so forth. */ * bitmap_get() and so forth. */
bool (*decode)(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info); bool (*decode)(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info);
} ProtoViewDecoder; } ProtoViewDecoder;
extern RawSamplesBuffer *RawSamples, *DetectedSamples; extern RawSamplesBuffer *RawSamples, *DetectedSamples;
@ -183,49 +180,79 @@ uint32_t radio_rx(ProtoViewApp* app);
void radio_idle(ProtoViewApp* app); void radio_idle(ProtoViewApp* app);
void radio_rx_end(ProtoViewApp* app); void radio_rx_end(ProtoViewApp* app);
void radio_sleep(ProtoViewApp* app); void radio_sleep(ProtoViewApp* app);
void raw_sampling_worker_start(ProtoViewApp *app); void raw_sampling_worker_start(ProtoViewApp* app);
void raw_sampling_worker_stop(ProtoViewApp *app); void raw_sampling_worker_stop(ProtoViewApp* app);
/* signal.c */ /* signal.c */
uint32_t duration_delta(uint32_t a, uint32_t b); uint32_t duration_delta(uint32_t a, uint32_t b);
void reset_current_signal(ProtoViewApp *app); void reset_current_signal(ProtoViewApp* app);
void scan_for_signal(ProtoViewApp *app); void scan_for_signal(ProtoViewApp* app);
bool bitmap_get(uint8_t *b, uint32_t blen, uint32_t bitpos); 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(uint8_t* b, uint32_t blen, uint32_t bitpos, bool val);
void bitmap_copy(uint8_t *d, uint32_t dlen, uint32_t doff, uint8_t *s, uint32_t slen, uint32_t soff, uint32_t count); void bitmap_copy(
void bitmap_set_pattern(uint8_t *b, uint32_t blen, uint32_t off, const char *pat); uint8_t* d,
void bitmap_reverse_bytes(uint8_t *p, uint32_t len); uint32_t dlen,
bool bitmap_match_bits(uint8_t *b, uint32_t blen, uint32_t bitpos, const char *bits); uint32_t doff,
uint32_t bitmap_seek_bits(uint8_t *b, uint32_t blen, uint32_t startpos, uint32_t maxbits, const char *bits); uint8_t* s,
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); uint32_t slen,
uint32_t convert_from_diff_manchester(uint8_t *buf, uint64_t buflen, uint8_t *bits, uint32_t len, uint32_t off, bool previous); uint32_t soff,
void init_msg_info(ProtoViewMsgInfo *i, ProtoViewApp *app); uint32_t count);
void free_msg_info(ProtoViewMsgInfo *i); void bitmap_set_pattern(uint8_t* b, uint32_t blen, uint32_t off, const char* pat);
void bitmap_reverse_bytes(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);
uint32_t convert_from_diff_manchester(
uint8_t* buf,
uint64_t buflen,
uint8_t* bits,
uint32_t len,
uint32_t off,
bool previous);
void init_msg_info(ProtoViewMsgInfo* i, ProtoViewApp* app);
void free_msg_info(ProtoViewMsgInfo* i);
/* signal_file.c */ /* signal_file.c */
bool save_signal(ProtoViewApp *app, const char *filename); bool save_signal(ProtoViewApp* app, const char* filename);
/* view_*.c */ /* view_*.c */
void render_view_raw_pulses(Canvas *const canvas, ProtoViewApp *app); void render_view_raw_pulses(Canvas* const canvas, ProtoViewApp* app);
void process_input_raw_pulses(ProtoViewApp *app, InputEvent input); void process_input_raw_pulses(ProtoViewApp* app, InputEvent input);
void render_view_settings(Canvas *const canvas, ProtoViewApp *app); void render_view_settings(Canvas* const canvas, ProtoViewApp* app);
void process_input_settings(ProtoViewApp *app, InputEvent input); void process_input_settings(ProtoViewApp* app, InputEvent input);
void render_view_info(Canvas *const canvas, ProtoViewApp *app); void render_view_info(Canvas* const canvas, ProtoViewApp* app);
void process_input_info(ProtoViewApp *app, InputEvent input); void process_input_info(ProtoViewApp* app, InputEvent input);
void render_view_direct_sampling(Canvas *const canvas, ProtoViewApp *app); void render_view_direct_sampling(Canvas* const canvas, ProtoViewApp* app);
void process_input_direct_sampling(ProtoViewApp *app, InputEvent input); void process_input_direct_sampling(ProtoViewApp* app, InputEvent input);
void view_enter_direct_sampling(ProtoViewApp *app); void view_enter_direct_sampling(ProtoViewApp* app);
void view_exit_direct_sampling(ProtoViewApp *app); void view_exit_direct_sampling(ProtoViewApp* app);
void view_exit_settings(ProtoViewApp *app); void view_exit_settings(ProtoViewApp* app);
/* ui.c */ /* ui.c */
int get_current_subview(ProtoViewApp *app); int get_current_subview(ProtoViewApp* app);
void show_available_subviews(Canvas *canvas, ProtoViewApp *app, int last_subview); void show_available_subviews(Canvas* canvas, ProtoViewApp* app, int last_subview);
bool process_subview_updown(ProtoViewApp *app, InputEvent input, int last_subview); bool process_subview_updown(ProtoViewApp* app, InputEvent input, int last_subview);
void canvas_draw_str_with_border(Canvas* canvas, uint8_t x, uint8_t y, const char* str, Color text_color, Color border_color); void canvas_draw_str_with_border(
void show_keyboard(ProtoViewApp *app, char *buffer, uint32_t buflen, Canvas* canvas,
void (*done_callback)(void*)); uint8_t x,
void dismiss_keyboard(ProtoViewApp *app); uint8_t y,
const char* str,
Color text_color,
Color border_color);
void show_keyboard(ProtoViewApp* app, char* buffer, uint32_t buflen, void (*done_callback)(void*));
void dismiss_keyboard(ProtoViewApp* app);
/* crc.c */ /* crc.c */
uint8_t crc8(const uint8_t *data, size_t len, uint8_t init, uint8_t poly); uint8_t crc8(const uint8_t* data, size_t len, uint8_t init, uint8_t poly);

View file

@ -8,15 +8,15 @@
#include "app_buffer.h" #include "app_buffer.h"
/* Allocate and initialize a samples buffer. */ /* Allocate and initialize a samples buffer. */
RawSamplesBuffer *raw_samples_alloc(void) { RawSamplesBuffer* raw_samples_alloc(void) {
RawSamplesBuffer *buf = malloc(sizeof(*buf)); RawSamplesBuffer* buf = malloc(sizeof(*buf));
buf->mutex = furi_mutex_alloc(FuriMutexTypeNormal); buf->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
raw_samples_reset(buf); raw_samples_reset(buf);
return buf; return buf;
} }
/* Free a sample buffer. Should be called when the mutex is released. */ /* Free a sample buffer. Should be called when the mutex is released. */
void raw_samples_free(RawSamplesBuffer *s) { void raw_samples_free(RawSamplesBuffer* s) {
furi_mutex_free(s->mutex); furi_mutex_free(s->mutex);
free(s); free(s);
} }
@ -24,35 +24,34 @@ void raw_samples_free(RawSamplesBuffer *s) {
/* This just set all the samples to zero and also resets the internal /* This just set all the samples to zero and also resets the internal
* index. There is no need to call it after raw_samples_alloc(), but only * index. There is no need to call it after raw_samples_alloc(), but only
* when one wants to reset the whole buffer of samples. */ * when one wants to reset the whole buffer of samples. */
void raw_samples_reset(RawSamplesBuffer *s) { void raw_samples_reset(RawSamplesBuffer* s) {
furi_mutex_acquire(s->mutex,FuriWaitForever); furi_mutex_acquire(s->mutex, FuriWaitForever);
s->total = RAW_SAMPLES_NUM; s->total = RAW_SAMPLES_NUM;
s->idx = 0; s->idx = 0;
s->short_pulse_dur = 0; s->short_pulse_dur = 0;
memset(s->samples,0,sizeof(s->samples)); memset(s->samples, 0, sizeof(s->samples));
furi_mutex_release(s->mutex); furi_mutex_release(s->mutex);
} }
/* Set the raw sample internal index so that what is currently at /* Set the raw sample internal index so that what is currently at
* offset 'offset', will appear to be at 0 index. */ * offset 'offset', will appear to be at 0 index. */
void raw_samples_center(RawSamplesBuffer *s, uint32_t offset) { void raw_samples_center(RawSamplesBuffer* s, uint32_t offset) {
s->idx = (s->idx+offset) % RAW_SAMPLES_NUM; s->idx = (s->idx + offset) % RAW_SAMPLES_NUM;
} }
/* Add the specified sample in the circular buffer. */ /* Add the specified sample in the circular buffer. */
void raw_samples_add(RawSamplesBuffer *s, bool level, uint32_t dur) { void raw_samples_add(RawSamplesBuffer* s, bool level, uint32_t dur) {
furi_mutex_acquire(s->mutex,FuriWaitForever); furi_mutex_acquire(s->mutex, FuriWaitForever);
s->samples[s->idx].level = level; s->samples[s->idx].level = level;
s->samples[s->idx].dur = dur; s->samples[s->idx].dur = dur;
s->idx = (s->idx+1) % RAW_SAMPLES_NUM; s->idx = (s->idx + 1) % RAW_SAMPLES_NUM;
furi_mutex_release(s->mutex); furi_mutex_release(s->mutex);
} }
/* Get the sample from the buffer. It is possible to use out of range indexes /* Get the sample from the buffer. It is possible to use out of range indexes
* as 'idx' because the modulo operation will rewind back from the start. */ * as 'idx' because the modulo operation will rewind back from the start. */
void raw_samples_get(RawSamplesBuffer *s, uint32_t idx, bool *level, uint32_t *dur) void raw_samples_get(RawSamplesBuffer* s, uint32_t idx, bool* level, uint32_t* dur) {
{ furi_mutex_acquire(s->mutex, FuriWaitForever);
furi_mutex_acquire(s->mutex,FuriWaitForever);
idx = (s->idx + idx) % RAW_SAMPLES_NUM; idx = (s->idx + idx) % RAW_SAMPLES_NUM;
*level = s->samples[idx].level; *level = s->samples[idx].level;
*dur = s->samples[idx].dur; *dur = s->samples[idx].dur;
@ -60,12 +59,12 @@ void raw_samples_get(RawSamplesBuffer *s, uint32_t idx, bool *level, uint32_t *d
} }
/* Copy one buffer to the other, including current index. */ /* Copy one buffer to the other, including current index. */
void raw_samples_copy(RawSamplesBuffer *dst, RawSamplesBuffer *src) { void raw_samples_copy(RawSamplesBuffer* dst, RawSamplesBuffer* src) {
furi_mutex_acquire(src->mutex,FuriWaitForever); furi_mutex_acquire(src->mutex, FuriWaitForever);
furi_mutex_acquire(dst->mutex,FuriWaitForever); furi_mutex_acquire(dst->mutex, FuriWaitForever);
dst->idx = src->idx; dst->idx = src->idx;
dst->short_pulse_dur = src->short_pulse_dur; dst->short_pulse_dur = src->short_pulse_dur;
memcpy(dst->samples,src->samples,sizeof(dst->samples)); memcpy(dst->samples, src->samples, sizeof(dst->samples));
furi_mutex_release(src->mutex); furi_mutex_release(src->mutex);
furi_mutex_release(dst->mutex); furi_mutex_release(dst->mutex);
} }

View file

@ -4,16 +4,17 @@
/* Our circular buffer of raw samples, used in order to display /* Our circular buffer of raw samples, used in order to display
* the signal. */ * the signal. */
#define RAW_SAMPLES_NUM 2048 /* Use a power of two: we take the modulo #define RAW_SAMPLES_NUM \
2048 /* Use a power of two: we take the modulo
of the index quite often to normalize inside of the index quite often to normalize inside
the range, and division is slow. */ the range, and division is slow. */
typedef struct RawSamplesBuffer { typedef struct RawSamplesBuffer {
FuriMutex *mutex; FuriMutex* mutex;
struct { struct {
uint16_t level:1; uint16_t level : 1;
uint16_t dur:15; uint16_t dur : 15;
} samples[RAW_SAMPLES_NUM]; } samples[RAW_SAMPLES_NUM];
uint32_t idx; /* Current idx (next to write). */ uint32_t idx; /* Current idx (next to write). */
uint32_t total; /* Total samples: same as RAW_SAMPLES_NUM, we provide uint32_t total; /* Total samples: same as RAW_SAMPLES_NUM, we provide
this field for a cleaner interface with the user, but this field for a cleaner interface with the user, but
we always use RAW_SAMPLES_NUM when taking the modulo so we always use RAW_SAMPLES_NUM when taking the modulo so
@ -22,10 +23,10 @@ typedef struct RawSamplesBuffer {
uint32_t short_pulse_dur; /* Duration of the shortest pulse. */ uint32_t short_pulse_dur; /* Duration of the shortest pulse. */
} RawSamplesBuffer; } RawSamplesBuffer;
RawSamplesBuffer *raw_samples_alloc(void); RawSamplesBuffer* raw_samples_alloc(void);
void raw_samples_reset(RawSamplesBuffer *s); void raw_samples_reset(RawSamplesBuffer* s);
void raw_samples_center(RawSamplesBuffer *s, uint32_t offset); void raw_samples_center(RawSamplesBuffer* s, uint32_t offset);
void raw_samples_add(RawSamplesBuffer *s, bool level, uint32_t dur); 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_get(RawSamplesBuffer* s, uint32_t idx, bool* level, uint32_t* dur);
void raw_samples_copy(RawSamplesBuffer *dst, RawSamplesBuffer *src); void raw_samples_copy(RawSamplesBuffer* dst, RawSamplesBuffer* src);
void raw_samples_free(RawSamplesBuffer *s); void raw_samples_free(RawSamplesBuffer* s);

View file

@ -9,18 +9,20 @@
#include <furi_hal_spi.h> #include <furi_hal_spi.h>
#include <furi_hal_interrupt.h> #include <furi_hal_interrupt.h>
void raw_sampling_worker_start(ProtoViewApp *app); void raw_sampling_worker_start(ProtoViewApp* app);
void raw_sampling_worker_stop(ProtoViewApp *app); void raw_sampling_worker_stop(ProtoViewApp* app);
ProtoViewModulation ProtoViewModulations[] = { ProtoViewModulation ProtoViewModulations[] = {
{"OOK 650Khz", "FuriHalSubGhzPresetOok650Async", {"OOK 650Khz", "FuriHalSubGhzPresetOok650Async", FuriHalSubGhzPresetOok650Async, NULL},
FuriHalSubGhzPresetOok650Async, NULL}, {"OOK 270Khz", "FuriHalSubGhzPresetOok270Async", FuriHalSubGhzPresetOok270Async, NULL},
{"OOK 270Khz", "FuriHalSubGhzPresetOok270Async", {"2FSK 2.38Khz",
FuriHalSubGhzPresetOok270Async, NULL}, "FuriHalSubGhzPreset2FSKDev238Async",
{"2FSK 2.38Khz", "FuriHalSubGhzPreset2FSKDev238Async", FuriHalSubGhzPreset2FSKDev238Async,
FuriHalSubGhzPreset2FSKDev238Async, NULL}, NULL},
{"2FSK 47.6Khz", "FuriHalSubGhzPreset2FSKDev476Async", {"2FSK 47.6Khz",
FuriHalSubGhzPreset2FSKDev476Async, NULL}, "FuriHalSubGhzPreset2FSKDev476Async",
FuriHalSubGhzPreset2FSKDev476Async,
NULL},
{"TPMS 1 (FSK)", NULL, 0, (uint8_t*)protoview_subghz_tpms1_fsk_async_regs}, {"TPMS 1 (FSK)", NULL, 0, (uint8_t*)protoview_subghz_tpms1_fsk_async_regs},
{"TPMS 2 (OOK)", NULL, 0, (uint8_t*)protoview_subghz_tpms2_ook_async_regs}, {"TPMS 2 (OOK)", NULL, 0, (uint8_t*)protoview_subghz_tpms2_ook_async_regs},
{"TPMS 3 (FSK)", NULL, 0, (uint8_t*)protoview_subghz_tpms3_fsk_async_regs}, {"TPMS 3 (FSK)", NULL, 0, (uint8_t*)protoview_subghz_tpms3_fsk_async_regs},
@ -40,7 +42,7 @@ void radio_begin(ProtoViewApp* app) {
/* The CC1101 preset can be either one of the standard presets, if /* The CC1101 preset can be either one of the standard presets, if
* the modulation "custom" field is NULL, or a custom preset we * the modulation "custom" field is NULL, or a custom preset we
* defined in custom_presets.h. */ * defined in custom_presets.h. */
if (ProtoViewModulations[app->modulation].custom == NULL) if(ProtoViewModulations[app->modulation].custom == NULL)
furi_hal_subghz_load_preset(ProtoViewModulations[app->modulation].preset); furi_hal_subghz_load_preset(ProtoViewModulations[app->modulation].preset);
else else
furi_hal_subghz_load_custom_preset(ProtoViewModulations[app->modulation].custom); furi_hal_subghz_load_custom_preset(ProtoViewModulations[app->modulation].custom);
@ -52,10 +54,10 @@ void radio_begin(ProtoViewApp* app) {
uint32_t radio_rx(ProtoViewApp* app) { uint32_t radio_rx(ProtoViewApp* app) {
furi_assert(app); furi_assert(app);
if(!furi_hal_subghz_is_frequency_valid(app->frequency)) { if(!furi_hal_subghz_is_frequency_valid(app->frequency)) {
furi_crash(TAG" Incorrect RX frequency."); furi_crash(TAG " Incorrect RX frequency.");
} }
if (app->txrx->txrx_state == TxRxStateRx) return app->frequency; if(app->txrx->txrx_state == TxRxStateRx) return app->frequency;
furi_hal_subghz_idle(); /* Put it into idle state in case it is sleeping. */ furi_hal_subghz_idle(); /* Put it into idle state in case it is sleeping. */
uint32_t value = furi_hal_subghz_set_frequency_and_path(app->frequency); uint32_t value = furi_hal_subghz_set_frequency_and_path(app->frequency);
@ -63,10 +65,8 @@ uint32_t radio_rx(ProtoViewApp* app) {
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
furi_hal_subghz_flush_rx(); furi_hal_subghz_flush_rx();
furi_hal_subghz_rx(); furi_hal_subghz_rx();
if (!app->txrx->debug_timer_sampling) { if(!app->txrx->debug_timer_sampling) {
furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, app->txrx->worker);
furi_hal_subghz_start_async_rx(subghz_worker_rx_callback,
app->txrx->worker);
subghz_worker_start(app->txrx->worker); subghz_worker_start(app->txrx->worker);
} else { } else {
raw_sampling_worker_start(app); raw_sampling_worker_start(app);
@ -78,8 +78,8 @@ uint32_t radio_rx(ProtoViewApp* app) {
/* Stop subghz worker (if active), put radio on idle state. */ /* Stop subghz worker (if active), put radio on idle state. */
void radio_rx_end(ProtoViewApp* app) { void radio_rx_end(ProtoViewApp* app) {
furi_assert(app); furi_assert(app);
if (app->txrx->txrx_state == TxRxStateRx) { if(app->txrx->txrx_state == TxRxStateRx) {
if (!app->txrx->debug_timer_sampling) { if(!app->txrx->debug_timer_sampling) {
if(subghz_worker_is_running(app->txrx->worker)) { if(subghz_worker_is_running(app->txrx->worker)) {
subghz_worker_stop(app->txrx->worker); subghz_worker_stop(app->txrx->worker);
furi_hal_subghz_stop_async_rx(); furi_hal_subghz_stop_async_rx();
@ -95,7 +95,7 @@ void radio_rx_end(ProtoViewApp* app) {
/* Put radio on sleep. */ /* Put radio on sleep. */
void radio_sleep(ProtoViewApp* app) { void radio_sleep(ProtoViewApp* app) {
furi_assert(app); furi_assert(app);
if (app->txrx->txrx_state == TxRxStateRx) { if(app->txrx->txrx_state == TxRxStateRx) {
/* We can't go from having an active RX worker to sleeping. /* We can't go from having an active RX worker to sleeping.
* Stop the RX subsystems first. */ * Stop the RX subsystems first. */
radio_rx_end(app); radio_rx_end(app);
@ -111,15 +111,15 @@ void radio_sleep(ProtoViewApp* app) {
* Flipper system. * Flipper system.
* ===========================================================================*/ * ===========================================================================*/
void protoview_timer_isr(void *ctx) { void protoview_timer_isr(void* ctx) {
ProtoViewApp *app = ctx; ProtoViewApp* app = ctx;
bool level = furi_hal_gpio_read(&gpio_cc1101_g0); bool level = furi_hal_gpio_read(&gpio_cc1101_g0);
if (app->txrx->last_g0_value != level) { if(app->txrx->last_g0_value != level) {
uint32_t now = DWT->CYCCNT; uint32_t now = DWT->CYCCNT;
uint32_t dur = now - app->txrx->last_g0_change_time; uint32_t dur = now - app->txrx->last_g0_change_time;
dur /= furi_hal_cortex_instructions_per_microsecond(); dur /= furi_hal_cortex_instructions_per_microsecond();
if (dur > 15000) dur = 15000; if(dur > 15000) dur = 15000;
raw_samples_add(RawSamples, app->txrx->last_g0_value, dur); raw_samples_add(RawSamples, app->txrx->last_g0_value, dur);
app->txrx->last_g0_value = level; app->txrx->last_g0_value = level;
app->txrx->last_g0_change_time = now; app->txrx->last_g0_change_time = now;
@ -127,13 +127,13 @@ void protoview_timer_isr(void *ctx) {
LL_TIM_ClearFlag_UPDATE(TIM2); LL_TIM_ClearFlag_UPDATE(TIM2);
} }
void raw_sampling_worker_start(ProtoViewApp *app) { void raw_sampling_worker_start(ProtoViewApp* app) {
UNUSED(app); UNUSED(app);
LL_TIM_InitTypeDef tim_init = { LL_TIM_InitTypeDef tim_init = {
.Prescaler = 63, /* CPU frequency is ~64Mhz. */ .Prescaler = 63, /* CPU frequency is ~64Mhz. */
.CounterMode = LL_TIM_COUNTERMODE_UP, .CounterMode = LL_TIM_COUNTERMODE_UP,
.Autoreload = 5, /* Sample every 5 us */ .Autoreload = 5, /* Sample every 5 us */
}; };
LL_TIM_Init(TIM2, &tim_init); LL_TIM_Init(TIM2, &tim_init);
@ -146,7 +146,7 @@ void raw_sampling_worker_start(ProtoViewApp *app) {
FURI_LOG_E(TAG, "Timer enabled"); FURI_LOG_E(TAG, "Timer enabled");
} }
void raw_sampling_worker_stop(ProtoViewApp *app) { void raw_sampling_worker_stop(ProtoViewApp* app) {
UNUSED(app); UNUSED(app);
FURI_CRITICAL_ENTER(); FURI_CRITICAL_ENTER();
LL_TIM_DisableCounter(TIM2); LL_TIM_DisableCounter(TIM2);

View file

@ -3,14 +3,13 @@
/* CRC8 with the specified initialization value 'init' and /* CRC8 with the specified initialization value 'init' and
* polynomial 'poly'. */ * polynomial 'poly'. */
uint8_t crc8(const uint8_t *data, size_t len, uint8_t init, uint8_t poly) uint8_t crc8(const uint8_t* data, size_t len, uint8_t init, uint8_t poly) {
{
uint8_t crc = init; uint8_t crc = init;
size_t i, j; size_t i, j;
for (i = 0; i < len; i++) { for(i = 0; i < len; i++) {
crc ^= data[i]; crc ^= data[i];
for (j = 0; j < 8; j++) { for(j = 0; j < 8; j++) {
if ((crc & 0x80) != 0) if((crc & 0x80) != 0)
crc = (uint8_t)((crc << 1) ^ poly); crc = (uint8_t)((crc << 1) ^ poly);
else else
crc <<= 1; crc <<= 1;

View file

@ -76,7 +76,8 @@ static uint8_t protoview_subghz_tpms1_fsk_async_regs[][2] = {
// // Modem Configuration // // Modem Configuration
{CC1101_MDMCFG0, 0x00}, {CC1101_MDMCFG0, 0x00},
{CC1101_MDMCFG1, 0x02}, {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_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_MDMCFG3, 0x93}, // Data rate is 20kBaud
{CC1101_MDMCFG4, 0x59}, // Rx bandwidth filter is 325 kHz {CC1101_MDMCFG4, 0x59}, // Rx bandwidth filter is 325 kHz
{CC1101_DEVIATN, 0x41}, // Deviation 28.56 kHz {CC1101_DEVIATN, 0x41}, // Deviation 28.56 kHz
@ -168,7 +169,8 @@ static uint8_t protoview_subghz_tpms3_fsk_async_regs[][2] = {
// // Modem Configuration // // Modem Configuration
{CC1101_MDMCFG0, 0x00}, {CC1101_MDMCFG0, 0x00},
{CC1101_MDMCFG1, 0x02}, {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_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_MDMCFG3, 0x93}, // Data rate is 40kBaud
{CC1101_MDMCFG4, 0x6A}, // 6 = BW filter 270kHz, A = Data rate exp {CC1101_MDMCFG4, 0x6A}, // 6 = BW filter 270kHz, A = Data rate exp
{CC1101_DEVIATN, 0x41}, // Deviation 28kHz {CC1101_DEVIATN, 0x41}, // Deviation 28kHz
@ -240,5 +242,3 @@ static uint8_t protoview_subghz_tpms4_fsk_async_regs[][2] = {
/* End */ /* End */
{0, 0}, {0, 0},
}; };

View file

@ -14,7 +14,7 @@ const SubGhzProtocol subghz_protocol_protoview;
/* The feed() method puts data in the RawSamples global (protected by /* The feed() method puts data in the RawSamples global (protected by
* a mutex). */ * a mutex). */
extern RawSamplesBuffer *RawSamples; extern RawSamplesBuffer* RawSamples;
/* This is totally dummy: we just define the decoder base for the async /* This is totally dummy: we just define the decoder base for the async
* system to work but we don't really use it if not to collect raw * system to work but we don't really use it if not to collect raw
@ -26,8 +26,7 @@ typedef struct SubGhzProtocolDecoderprotoview {
void* subghz_protocol_decoder_protoview_alloc(SubGhzEnvironment* environment) { void* subghz_protocol_decoder_protoview_alloc(SubGhzEnvironment* environment) {
UNUSED(environment); UNUSED(environment);
SubGhzProtocolDecoderprotoview* instance = SubGhzProtocolDecoderprotoview* instance = malloc(sizeof(SubGhzProtocolDecoderprotoview));
malloc(sizeof(SubGhzProtocolDecoderprotoview));
instance->base.protocol = &subghz_protocol_protoview; instance->base.protocol = &subghz_protocol_protoview;
return instance; return instance;
} }
@ -66,8 +65,7 @@ uint8_t subghz_protocol_decoder_protoview_get_hash_data(void* context) {
bool subghz_protocol_decoder_protoview_serialize( bool subghz_protocol_decoder_protoview_serialize(
void* context, void* context,
FlipperFormat* flipper_format, FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) SubGhzRadioPreset* preset) {
{
UNUSED(context); UNUSED(context);
UNUSED(flipper_format); UNUSED(flipper_format);
UNUSED(preset); UNUSED(preset);
@ -75,15 +73,13 @@ bool subghz_protocol_decoder_protoview_serialize(
} }
/* Not used. */ /* Not used. */
bool subghz_protocol_decoder_protoview_deserialize(void* context, FlipperFormat* flipper_format) bool subghz_protocol_decoder_protoview_deserialize(void* context, FlipperFormat* flipper_format) {
{
UNUSED(context); UNUSED(context);
UNUSED(flipper_format); UNUSED(flipper_format);
return false; return false;
} }
void subhz_protocol_decoder_protoview_get_string(void* context, FuriString* output) void subhz_protocol_decoder_protoview_get_string(void* context, FuriString* output) {
{
furi_assert(context); furi_assert(context);
furi_string_cat_printf(output, "Protoview"); furi_string_cat_printf(output, "Protoview");
} }
@ -116,5 +112,4 @@ const SubGhzProtocol* protoview_protocol_registry_items[] = {
const SubGhzProtocolRegistry protoview_protocol_registry = { const SubGhzProtocolRegistry protoview_protocol_registry = {
.items = protoview_protocol_registry_items, .items = protoview_protocol_registry_items,
.size = COUNT_OF(protoview_protocol_registry_items) .size = COUNT_OF(protoview_protocol_registry_items)};
};

View file

@ -9,9 +9,9 @@
#include "../app.h" #include "../app.h"
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) { static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info) {
if (numbits < 30) return false; if(numbits < 30) return false;
const char *sync_patterns[3] = { const char* sync_patterns[3] = {
"10000000000000000000000000000001", /* 30 zero bits. */ "10000000000000000000000000000001", /* 30 zero bits. */
"100000000000000000000000000000001", /* 31 zero bits. */ "100000000000000000000000000000001", /* 31 zero bits. */
"1000000000000000000000000000000001", /* 32 zero bits. */ "1000000000000000000000000000000001", /* 32 zero bits. */
@ -19,32 +19,29 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
uint32_t off; uint32_t off;
int j; int j;
for (j = 0; j < 3; j++) { for(j = 0; j < 3; j++) {
off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_patterns[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) break;
} }
if (off == BITMAP_SEEK_NOT_FOUND) return false; if(off == BITMAP_SEEK_NOT_FOUND) return false;
if (DEBUG_MSG) FURI_LOG_E(TAG, "B4B1 preamble at: %lu",off); if(DEBUG_MSG) FURI_LOG_E(TAG, "B4B1 preamble at: %lu", off);
info->start_off = off; info->start_off = off;
// Seek data setction. Why -1? Last bit is data. // Seek data setction. Why -1? Last bit is data.
off += strlen(sync_patterns[j])-1; off += strlen(sync_patterns[j]) - 1;
uint8_t d[3]; /* 24 bits of data. */ uint8_t d[3]; /* 24 bits of data. */
uint32_t decoded = uint32_t decoded = convert_from_line_code(d, sizeof(d), bits, numbytes, off, "1000", "1110");
convert_from_line_code(d,sizeof(d),bits,numbytes,off,"1000","1110");
if (DEBUG_MSG) FURI_LOG_E(TAG, "B4B1 decoded: %lu",decoded); if(DEBUG_MSG) FURI_LOG_E(TAG, "B4B1 decoded: %lu", decoded);
if (decoded < 24) return false; if(decoded < 24) return false;
off += 24*4; // seek to end symbol offset to calculate the length. off += 24 * 4; // seek to end symbol offset to calculate the length.
off++; // In this protocol there is a final pulse as terminator. off++; // In this protocol there is a final pulse as terminator.
info->pulses_count = off - info->start_off; info->pulses_count = off - info->start_off;
snprintf(info->name,PROTOVIEW_MSG_STR_LEN,"PT/SC remote"); 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]); snprintf(info->raw, PROTOVIEW_MSG_STR_LEN, "%02X%02X%02X", d[0], d[1], d[2]);
return true; return true;
} }
ProtoViewDecoder B4B1Decoder = { ProtoViewDecoder B4B1Decoder = {"B4B1", decode};
"B4B1", decode
};

View file

@ -24,16 +24,16 @@
#include "../app.h" #include "../app.h"
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) { static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info) {
/* In the sync pattern, we require the 12 high/low pulses and at least /* In the sync pattern, we require the 12 high/low pulses and at least
* half the gap we expect (5 pulses times, one is the final zero in the * half the gap we expect (5 pulses times, one is the final zero in the
* 24 symbols high/low sequence, then other 4). */ * 24 symbols high/low sequence, then other 4). */
const char *sync_pattern = "101010101010101010101010" "0000"; const char* sync_pattern = "101010101010101010101010"
uint8_t sync_len = 24+4; "0000";
if (numbits-sync_len+sync_len < 3*66) return false; uint8_t sync_len = 24 + 4;
uint32_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern); if(numbits - sync_len + sync_len < 3 * 66) return false;
if (off == BITMAP_SEEK_NOT_FOUND) return false; uint32_t off = bitmap_seek_bits(bits, numbytes, 0, numbits, sync_pattern);
if(off == BITMAP_SEEK_NOT_FOUND) return false;
info->start_off = off; info->start_off = off;
off += sync_len; // Seek start of message. off += sync_len; // Seek start of message.
@ -42,50 +42,57 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
* symbols of gap, to avoid missing the signal for a matter of wrong * symbols of gap, to avoid missing the signal for a matter of wrong
* timing. */ * timing. */
uint8_t gap_len = 0; uint8_t gap_len = 0;
while(gap_len <= 7 && bitmap_get(bits,numbytes,off+gap_len) == 0) while(gap_len <= 7 && bitmap_get(bits, numbytes, off + gap_len) == 0) gap_len++;
gap_len++; if(gap_len < 3 || gap_len > 7) return false;
if (gap_len < 3 || gap_len > 7) return false;
off += gap_len; off += gap_len;
FURI_LOG_E(TAG, "Keeloq preamble+sync found"); FURI_LOG_E(TAG, "Keeloq preamble+sync found");
uint8_t raw[9] = {0}; uint8_t raw[9] = {0};
uint32_t decoded = uint32_t decoded = convert_from_line_code(
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off, raw, sizeof(raw), bits, numbytes, off, "110", "100"); /* Pulse width modulation. */
"110","100"); /* Pulse width modulation. */
FURI_LOG_E(TAG, "Keeloq decoded bits: %lu", decoded); FURI_LOG_E(TAG, "Keeloq decoded bits: %lu", decoded);
if (decoded < 66) return false; /* Require the full 66 bits. */ if(decoded < 66) return false; /* Require the full 66 bits. */
info->pulses_count = (off+66*3) - info->start_off; info->pulses_count = (off + 66 * 3) - info->start_off;
bitmap_reverse_bytes(raw,sizeof(raw)); /* Keeloq is LSB first. */ bitmap_reverse_bytes(raw, sizeof(raw)); /* Keeloq is LSB first. */
int buttons = raw[7]>>4; int buttons = raw[7] >> 4;
int s3 = (buttons&1) != 0; int s3 = (buttons & 1) != 0;
int s0 = (buttons&2) != 0; int s0 = (buttons & 2) != 0;
int s1 = (buttons&4) != 0; int s1 = (buttons & 4) != 0;
int s2 = (buttons&8) != 0; int s2 = (buttons & 8) != 0;
int remote_id = ((raw[7]&0x0f) << 24) | int remote_id = ((raw[7] & 0x0f) << 24) | (raw[6] << 16) | (raw[5] << 8) | (raw[4] << 0);
(raw[6] << 16) | int lowbat = raw[8] & 0x80;
(raw[5] << 8) |
(raw[4] << 0);
int lowbat = raw[8]&0x80;
snprintf(info->name,sizeof(info->name),"%s","Keeloq remote"); snprintf(info->name, sizeof(info->name), "%s", "Keeloq remote");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X", snprintf(
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5], info->raw,
raw[6],raw[7],raw[8]); sizeof(info->raw),
snprintf(info->info1,sizeof(info->info1),"Encrpyted %02X%02X%02X%02X", "%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[3],raw[2],raw[1],raw[0]); raw[0],
snprintf(info->info2,sizeof(info->info2),"ID %08X", remote_id); raw[1],
snprintf(info->info3,sizeof(info->info3),"s0-s3: %d%d%d%d", raw[2],
s0,s1,s2,s3); raw[3],
snprintf(info->info4,sizeof(info->info4),"Low battery? %s", raw[4],
lowbat ? "yes" : "no"); raw[5],
raw[6],
raw[7],
raw[8]);
snprintf(
info->info1,
sizeof(info->info1),
"Encrpyted %02X%02X%02X%02X",
raw[3],
raw[2],
raw[1],
raw[0]);
snprintf(info->info2, sizeof(info->info2), "ID %08X", remote_id);
snprintf(info->info3, sizeof(info->info3), "s0-s3: %d%d%d%d", s0, s1, s2, s3);
snprintf(info->info4, sizeof(info->info4), "Low battery? %s", lowbat ? "yes" : "no");
return true; return true;
} }
ProtoViewDecoder KeeloqDecoder = { ProtoViewDecoder KeeloqDecoder = {"Keeloq", decode};
"Keeloq", decode
};

View file

@ -6,11 +6,14 @@
#include "../app.h" #include "../app.h"
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) { static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info) {
if (numbits < 32) return false; if(numbits < 32) return false;
const char *sync_pattern = "01100110" "01100110" "10010110" "10010110"; const char* sync_pattern = "01100110"
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern); "01100110"
if (off == BITMAP_SEEK_NOT_FOUND) return false; "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"); FURI_LOG_E(TAG, "Oregon2 preamble+sync found");
info->start_off = off; info->start_off = off;
@ -18,50 +21,71 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
uint8_t buffer[8], raw[8] = {0}; uint8_t buffer[8], raw[8] = {0};
uint32_t decoded = uint32_t decoded =
convert_from_line_code(buffer,sizeof(buffer),bits,numbytes,off,"1001","0110"); convert_from_line_code(buffer, sizeof(buffer), bits, numbytes, off, "1001", "0110");
FURI_LOG_E(TAG, "Oregon2 decoded bits: %lu", decoded); FURI_LOG_E(TAG, "Oregon2 decoded bits: %lu", decoded);
if (decoded < 11*4) return false; /* Minimum len to extract some data. */ if(decoded < 11 * 4) return false; /* Minimum len to extract some data. */
info->pulses_count = (off+11*4*4) - info->start_off; info->pulses_count = (off + 11 * 4 * 4) - info->start_off;
char temp[3] = {0}, deviceid[2] = {0}, hum[2] = {0}; char temp[3] = {0}, deviceid[2] = {0}, hum[2] = {0};
for (int j = 0; j < 64; j += 4) { for(int j = 0; j < 64; j += 4) {
uint8_t nib[1]; uint8_t nib[1];
nib[0] = (bitmap_get(buffer,8,j+0) | nib[0] =
bitmap_get(buffer,8,j+1) << 1 | (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 + 2) << 2 | bitmap_get(buffer, 8, j + 3) << 3);
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]);
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));
raw[j/8] |= nib[0] << (4-(j%4)); switch(j / 4) {
switch(j/4) { case 1:
case 1: deviceid[0] |= nib[0]; break; deviceid[0] |= nib[0];
case 0: deviceid[0] |= nib[0] << 4; break; break;
case 3: deviceid[1] |= nib[0]; break; case 0:
case 2: deviceid[1] |= nib[0] << 4; break; deviceid[0] |= nib[0] << 4;
case 10: temp[0] = nib[0]; break; 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. */ /* Fixme: take the temperature sign from nibble 11. */
case 9: temp[1] = nib[0]; break; case 9:
case 8: temp[2] = nib[0]; break; temp[1] = nib[0];
case 13: hum[0] = nib[0]; break; break;
case 12: hum[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"); snprintf(info->name, sizeof(info->name), "%s", "Oregon v2.1");
/* The following line crashes the Flipper because of broken /* The following line crashes the Flipper because of broken
* snprintf() implementation. */ * snprintf() implementation. */
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X", snprintf(
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5], info->raw,
raw[6],raw[7]); sizeof(info->raw),
snprintf(info->info1,sizeof(info->info1),"Sensor ID %02X%02X", "%02X%02X%02X%02X%02X%02X%02X%02X",
deviceid[0], deviceid[1]); raw[0],
snprintf(info->info2,sizeof(info->info2),"Temperature %d%d.%d", raw[1],
temp[0],temp[1],temp[2]); raw[2],
snprintf(info->info3,sizeof(info->info3),"Humidity %d%d", raw[3],
hum[0],hum[1]); 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; return true;
} }
ProtoViewDecoder Oregon2Decoder = { ProtoViewDecoder Oregon2Decoder = {"Oregon2", decode};
"Oregon2", decode
};

View file

@ -7,57 +7,69 @@
#include "../../app.h" #include "../../app.h"
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) { static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info) {
/* We consider a preamble of 17 symbols. They are more, but the decoding /* We consider a preamble of 17 symbols. They are more, but the decoding
* is more likely to happen if we don't pretend to receive from the * is more likely to happen if we don't pretend to receive from the
* very start of the message. */ * very start of the message. */
uint32_t sync_len = 17; uint32_t sync_len = 17;
const char *sync_pattern = "10101010101010110"; const char* sync_pattern = "10101010101010110";
if (numbits-sync_len < 8*10) return false; /* Expect 10 bytes. */ if(numbits - sync_len < 8 * 10) return false; /* Expect 10 bytes. */
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern); uint64_t off = bitmap_seek_bits(bits, numbytes, 0, numbits, sync_pattern);
if (off == BITMAP_SEEK_NOT_FOUND) return false; if(off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Renault TPMS preamble+sync found"); FURI_LOG_E(TAG, "Renault TPMS preamble+sync found");
info->start_off = off; info->start_off = off;
off += sync_len; /* Skip preamble + sync. */ off += sync_len; /* Skip preamble + sync. */
uint8_t raw[10]; uint8_t raw[10];
uint32_t decoded = uint32_t decoded = convert_from_line_code(
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off, raw, sizeof(raw), bits, numbytes, off, "01", "10"); /* Manchester. */
"01","10"); /* Manchester. */
FURI_LOG_E(TAG, "Citroen TPMS decoded bits: %lu", decoded); FURI_LOG_E(TAG, "Citroen TPMS decoded bits: %lu", decoded);
if (decoded < 8*10) return false; /* Require the full 10 bytes. */ if(decoded < 8 * 10) return false; /* Require the full 10 bytes. */
/* Check the CRC. It's a simple XOR of bytes 1-9, the first byte /* Check the CRC. It's a simple XOR of bytes 1-9, the first byte
* is not included. The meaning of the first byte is unknown and * is not included. The meaning of the first byte is unknown and
* we don't display it. */ * we don't display it. */
uint8_t crc = 0; uint8_t crc = 0;
for (int j = 1; j < 10; j++) crc ^= raw[j]; for(int j = 1; j < 10; j++) crc ^= raw[j];
if (crc != 0) return false; /* Require sane checksum. */ if(crc != 0) return false; /* Require sane checksum. */
info->pulses_count = (off+8*10*2) - info->start_off; info->pulses_count = (off + 8 * 10 * 2) - info->start_off;
int repeat = raw[5] & 0xf; int repeat = raw[5] & 0xf;
float kpa = (float)raw[6]*1.364; float kpa = (float)raw[6] * 1.364;
int temp = raw[7]-50; int temp = raw[7] - 50;
int battery = raw[8]; /* This may be the battery. It's not clear. */ int battery = raw[8]; /* This may be the battery. It's not clear. */
snprintf(info->name,sizeof(info->name),"%s","Citroen TPMS"); snprintf(info->name, sizeof(info->name), "%s", "Citroen TPMS");
snprintf(info->raw,sizeof(info->raw), snprintf(
info->raw,
sizeof(info->raw),
"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5], raw[0],
raw[6],raw[7],raw[8],raw[9]); raw[1],
snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X%02X", raw[2],
raw[1],raw[2],raw[3],raw[4]); raw[3],
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f kpa", (double)kpa); raw[4],
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp); raw[5],
snprintf(info->info4,sizeof(info->info4),"Repeat %d, Bat %d", repeat, battery); raw[6],
raw[7],
raw[8],
raw[9]);
snprintf(
info->info1,
sizeof(info->info1),
"Tire ID %02X%02X%02X%02X",
raw[1],
raw[2],
raw[3],
raw[4]);
snprintf(info->info2, sizeof(info->info2), "Pressure %.2f kpa", (double)kpa);
snprintf(info->info3, sizeof(info->info3), "Temperature %d C", temp);
snprintf(info->info4, sizeof(info->info4), "Repeat %d, Bat %d", repeat, battery);
return true; return true;
} }
ProtoViewDecoder CitroenTPMSDecoder = { ProtoViewDecoder CitroenTPMSDecoder = {"Citroen TPMS", decode};
"Citroen TPMS", decode
};

View file

@ -10,58 +10,70 @@
#include "../../app.h" #include "../../app.h"
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) { static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info) {
const char* sync_pattern = "010101010101"
"0110";
uint8_t sync_len = 12 + 4; /* We just use 12 preamble symbols + sync. */
if(numbits - sync_len < 8 * 8) return false;
const char *sync_pattern = "010101010101" "0110"; uint64_t off = bitmap_seek_bits(bits, numbytes, 0, numbits, sync_pattern);
uint8_t sync_len = 12+4; /* We just use 12 preamble symbols + sync. */ if(off == BITMAP_SEEK_NOT_FOUND) return false;
if (numbits-sync_len < 8*8) return false;
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern);
if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Fort TPMS preamble+sync found"); FURI_LOG_E(TAG, "Fort TPMS preamble+sync found");
info->start_off = off; info->start_off = off;
off += sync_len; /* Skip preamble and sync. */ off += sync_len; /* Skip preamble and sync. */
uint8_t raw[8]; uint8_t raw[8];
uint32_t decoded = uint32_t decoded = convert_from_line_code(
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off, raw, sizeof(raw), bits, numbytes, off, "01", "10"); /* Manchester. */
"01","10"); /* Manchester. */
FURI_LOG_E(TAG, "Ford TPMS decoded bits: %lu", decoded); FURI_LOG_E(TAG, "Ford TPMS decoded bits: %lu", decoded);
if (decoded < 8*8) return false; /* Require the full 8 bytes. */ if(decoded < 8 * 8) return false; /* Require the full 8 bytes. */
/* CRC is just the sum of the first 7 bytes MOD 256. */ /* CRC is just the sum of the first 7 bytes MOD 256. */
uint8_t crc = 0; uint8_t crc = 0;
for (int j = 0; j < 7; j++) crc += raw[j]; for(int j = 0; j < 7; j++) crc += raw[j];
if (crc != raw[7]) return false; /* Require sane CRC. */ if(crc != raw[7]) return false; /* Require sane CRC. */
info->pulses_count = (off+8*8*2) - info->start_off; info->pulses_count = (off + 8 * 8 * 2) - info->start_off;
float psi = 0.25 * (((raw[6]&0x20)<<3)|raw[4]); float psi = 0.25 * (((raw[6] & 0x20) << 3) | raw[4]);
/* Temperature apperas to be valid only if the most significant /* Temperature apperas to be valid only if the most significant
* bit of the value is not set. Otherwise its meaning is unknown. * bit of the value is not set. Otherwise its meaning is unknown.
* Likely useful to alternatively send temperature or other info. */ * Likely useful to alternatively send temperature or other info. */
int temp = raw[5] & 0x80 ? 0 : raw[5]-56; int temp = raw[5] & 0x80 ? 0 : raw[5] - 56;
int flags = raw[5] & 0x7f; int flags = raw[5] & 0x7f;
int car_moving = (raw[6] & 0x44) == 0x44; int car_moving = (raw[6] & 0x44) == 0x44;
snprintf(info->name,sizeof(info->name),"%s","Ford TPMS"); snprintf(info->name, sizeof(info->name), "%s", "Ford TPMS");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X", snprintf(
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5], info->raw,
raw[6],raw[7]); sizeof(info->raw),
snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X%02X", "%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3]); raw[0],
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f psi", (double)psi); raw[1],
if (temp) raw[2],
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp); raw[3],
raw[4],
raw[5],
raw[6],
raw[7]);
snprintf(
info->info1,
sizeof(info->info1),
"Tire ID %02X%02X%02X%02X",
raw[0],
raw[1],
raw[2],
raw[3]);
snprintf(info->info2, sizeof(info->info2), "Pressure %.2f psi", (double)psi);
if(temp)
snprintf(info->info3, sizeof(info->info3), "Temperature %d C", temp);
else else
snprintf(info->info3,sizeof(info->info3),"Flags %d", flags); snprintf(info->info3, sizeof(info->info3), "Flags %d", flags);
snprintf(info->info4,sizeof(info->info4),"Moving %s", car_moving ? "yes" : "no"); snprintf(info->info4, sizeof(info->info4), "Moving %s", car_moving ? "yes" : "no");
return true; return true;
} }
ProtoViewDecoder FordTPMSDecoder = { ProtoViewDecoder FordTPMSDecoder = {"Ford TPMS", decode};
"Ford TPMS", decode
};

View file

@ -6,64 +6,69 @@
#include "../../app.h" #include "../../app.h"
#define USE_TEST_VECTOR 0 #define USE_TEST_VECTOR 0
static const char *test_vector = static const char* test_vector =
"...01010101010101010110" // Preamble + sync "...01010101010101010110" // Preamble + sync
/* The following is Marshal encoded, so each two characters are /* The following is Marshal encoded, so each two characters are
* actaully one bit. 01 = 0, 10 = 1. */ * actaully one bit. 01 = 0, 10 = 1. */
"010110010110" // Flags. "010110010110" // Flags.
"10011001101010011001" // Pressure, multiply by 0.75 to obtain kpa. "10011001101010011001" // Pressure, multiply by 0.75 to obtain kpa.
// 244 kpa here. // 244 kpa here.
"1010010110011010" // Temperature, subtract 30 to obtain celsius. 22C here. "1010010110011010" // Temperature, subtract 30 to obtain celsius. 22C here.
"1001010101101001" "1001010101101001"
"0101100110010101" "0101100110010101"
"1001010101100110" // Tire ID. 0x7AD779 here. "1001010101100110" // Tire ID. 0x7AD779 here.
"0101010101010101" "0101010101010101"
"0101010101010101" // Two FF bytes (usually). Unknown. "0101010101010101" // Two FF bytes (usually). Unknown.
"0110010101010101"; // CRC8 with (poly 7, initialization 0). "0110010101010101"; // CRC8 with (poly 7, initialization 0).
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) { 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. */
if (USE_TEST_VECTOR) { /* Test vector to check that decoding works. */ bitmap_set_pattern(bits, numbytes, 0, test_vector);
bitmap_set_pattern(bits,numbytes,0,test_vector);
numbits = strlen(test_vector); numbits = strlen(test_vector);
} }
if (numbits-12 < 9*8) return false; if(numbits - 12 < 9 * 8) return false;
const char *sync_pattern = "01010101010101010110"; const char* sync_pattern = "01010101010101010110";
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern); uint64_t off = bitmap_seek_bits(bits, numbytes, 0, numbits, sync_pattern);
if (off == BITMAP_SEEK_NOT_FOUND) return false; if(off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Renault TPMS preamble+sync found"); FURI_LOG_E(TAG, "Renault TPMS preamble+sync found");
info->start_off = off; info->start_off = off;
off += 20; /* Skip preamble. */ off += 20; /* Skip preamble. */
uint8_t raw[9]; uint8_t raw[9];
uint32_t decoded = uint32_t decoded = convert_from_line_code(
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off, raw, sizeof(raw), bits, numbytes, off, "01", "10"); /* Manchester. */
"01","10"); /* Manchester. */
FURI_LOG_E(TAG, "Renault TPMS decoded bits: %lu", decoded); FURI_LOG_E(TAG, "Renault TPMS decoded bits: %lu", decoded);
if (decoded < 8*9) return false; /* Require the full 9 bytes. */ if(decoded < 8 * 9) return false; /* Require the full 9 bytes. */
if (crc8(raw,8,0,7) != raw[8]) return false; /* Require sane CRC. */ if(crc8(raw, 8, 0, 7) != raw[8]) return false; /* Require sane CRC. */
info->pulses_count = (off+8*9*2) - info->start_off; info->pulses_count = (off + 8 * 9 * 2) - info->start_off;
float kpa = 0.75 *((uint32_t)((raw[0]&3)<<8) | raw[1]); float kpa = 0.75 * ((uint32_t)((raw[0] & 3) << 8) | raw[1]);
int temp = raw[2]-30; int temp = raw[2] - 30;
snprintf(info->name,sizeof(info->name),"%s","Renault TPMS"); snprintf(info->name, sizeof(info->name), "%s", "Renault TPMS");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X", snprintf(
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5], info->raw,
raw[6],raw[7],raw[8]); sizeof(info->raw),
snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X", "%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[3],raw[4],raw[5]); raw[0],
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f kpa", (double)kpa); raw[1],
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp); raw[2],
raw[3],
raw[4],
raw[5],
raw[6],
raw[7],
raw[8]);
snprintf(info->info1, sizeof(info->info1), "Tire ID %02X%02X%02X", raw[3], raw[4], raw[5]);
snprintf(info->info2, sizeof(info->info2), "Pressure %.2f kpa", (double)kpa);
snprintf(info->info3, sizeof(info->info3), "Temperature %d C", temp);
return true; return true;
} }
ProtoViewDecoder RenaultTPMSDecoder = { ProtoViewDecoder RenaultTPMSDecoder = {"Renault TPMS", decode};
"Renault TPMS", decode
};

View file

@ -11,20 +11,21 @@
#include "../../app.h" #include "../../app.h"
#define USE_TEST_VECTOR 0 #define USE_TEST_VECTOR 0
static const char *test_vector = "000000111101010101011010010110010110101001010110100110011001100101010101011010100110100110011010101010101010101010101010101010101010101010101010"; static const char* test_vector =
"000000111101010101011010010110010110101001010110100110011001100101010101011010100110100110011010101010101010101010101010101010101010101010101010";
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) { 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. */
if (USE_TEST_VECTOR) { /* Test vector to check that decoding works. */ bitmap_set_pattern(bits, numbytes, 0, test_vector);
bitmap_set_pattern(bits,numbytes,0,test_vector);
numbits = strlen(test_vector); numbits = strlen(test_vector);
} }
if (numbits < 64) return false; /* Preamble + data. */ if(numbits < 64) return false; /* Preamble + data. */
const char *sync_pattern = "1111010101" "01011010"; const char* sync_pattern = "1111010101"
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern); "01011010";
if (off == BITMAP_SEEK_NOT_FOUND) return false; uint64_t off = bitmap_seek_bits(bits, numbytes, 0, numbits, sync_pattern);
if(off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Schrader TPMS gap+preamble found"); FURI_LOG_E(TAG, "Schrader TPMS gap+preamble found");
info->start_off = off; info->start_off = off;
@ -33,36 +34,48 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
0011 = 0x3. */ 0011 = 0x3. */
uint8_t raw[8]; uint8_t raw[8];
uint32_t decoded = uint32_t decoded = convert_from_line_code(
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off, raw, sizeof(raw), bits, numbytes, off, "01", "10"); /* Manchester code. */
"01","10"); /* Manchester code. */
FURI_LOG_E(TAG, "Schrader TPMS decoded bits: %lu", decoded); FURI_LOG_E(TAG, "Schrader TPMS decoded bits: %lu", decoded);
if (decoded < 64) return false; /* Require the full 8 bytes. */ if(decoded < 64) return false; /* Require the full 8 bytes. */
raw[0] |= 0xf0; // Fix the preamble nibble for checksum computation. raw[0] |= 0xf0; // Fix the preamble nibble for checksum computation.
uint8_t cksum = crc8(raw,sizeof(raw)-1,0xf0,0x7); uint8_t cksum = crc8(raw, sizeof(raw) - 1, 0xf0, 0x7);
if (cksum != raw[7]) { if(cksum != raw[7]) {
FURI_LOG_E(TAG, "Schrader TPMS checksum mismatch"); FURI_LOG_E(TAG, "Schrader TPMS checksum mismatch");
return false; return false;
} }
info->pulses_count = (off+8*8*2) - info->start_off; info->pulses_count = (off + 8 * 8 * 2) - info->start_off;
float kpa = (float)raw[5]*2.5; float kpa = (float)raw[5] * 2.5;
int temp = raw[6]-50; int temp = raw[6] - 50;
snprintf(info->name,sizeof(info->name),"%s","Schrader TPMS"); snprintf(info->name, sizeof(info->name), "%s", "Schrader TPMS");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X", snprintf(
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5], info->raw,
raw[6],raw[7]); sizeof(info->raw),
snprintf(info->info1,sizeof(info->info1),"Tire ID %01X%02X%02X%02X", "%02X%02X%02X%02X%02X%02X%02X%02X",
raw[1]&7,raw[2],raw[3],raw[4]); /* Only 28 bits of ID, not 32. */ raw[0],
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f kpa", (double)kpa); raw[1],
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp); raw[2],
raw[3],
raw[4],
raw[5],
raw[6],
raw[7]);
snprintf(
info->info1,
sizeof(info->info1),
"Tire ID %01X%02X%02X%02X",
raw[1] & 7,
raw[2],
raw[3],
raw[4]); /* Only 28 bits of ID, not 32. */
snprintf(info->info2, sizeof(info->info2), "Pressure %.2f kpa", (double)kpa);
snprintf(info->info3, sizeof(info->info3), "Temperature %d C", temp);
return true; return true;
} }
ProtoViewDecoder SchraderTPMSDecoder = { ProtoViewDecoder SchraderTPMSDecoder = {"Schrader TPMS", decode};
"Schrader TPMS", decode
};

View file

@ -15,52 +15,65 @@
#include "../../app.h" #include "../../app.h"
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) { static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info) {
const char* sync_pattern = "010101010101"
"01100101";
uint8_t sync_len = 12 + 8; /* We just use 12 preamble symbols + sync. */
if(numbits - sync_len + 8 < 8 * 10) return false;
const char *sync_pattern = "010101010101" "01100101"; uint64_t off = bitmap_seek_bits(bits, numbytes, 0, numbits, sync_pattern);
uint8_t sync_len = 12+8; /* We just use 12 preamble symbols + sync. */ if(off == BITMAP_SEEK_NOT_FOUND) return false;
if (numbits-sync_len+8 < 8*10) return false;
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern);
if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Schrader EG53MA4 TPMS preamble+sync found"); FURI_LOG_E(TAG, "Schrader EG53MA4 TPMS preamble+sync found");
info->start_off = off; info->start_off = off;
off += sync_len-8; /* Skip preamble, not sync that is part of the data. */ off += sync_len - 8; /* Skip preamble, not sync that is part of the data. */
uint8_t raw[10]; uint8_t raw[10];
uint32_t decoded = uint32_t decoded = convert_from_line_code(
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off, raw, sizeof(raw), bits, numbytes, off, "01", "10"); /* Manchester code. */
"01","10"); /* Manchester code. */
FURI_LOG_E(TAG, "Schrader EG53MA4 TPMS decoded bits: %lu", decoded); FURI_LOG_E(TAG, "Schrader EG53MA4 TPMS decoded bits: %lu", decoded);
if (decoded < 10*8) return false; /* Require the full 10 bytes. */ if(decoded < 10 * 8) return false; /* Require the full 10 bytes. */
/* CRC is just all bytes added mod 256. */ /* CRC is just all bytes added mod 256. */
uint8_t crc = 0; uint8_t crc = 0;
for (int j = 0; j < 9; j++) crc += raw[j]; for(int j = 0; j < 9; j++) crc += raw[j];
if (crc != raw[9]) return false; /* Require sane CRC. */ if(crc != raw[9]) return false; /* Require sane CRC. */
info->pulses_count = (off+10*8*2) - info->start_off; info->pulses_count = (off + 10 * 8 * 2) - info->start_off;
/* To convert the raw pressure to kPa, RTL433 uses 2.5, but is likely /* To convert the raw pressure to kPa, RTL433 uses 2.5, but is likely
* wrong. Searching on Google for users experimenting with the value * wrong. Searching on Google for users experimenting with the value
* reported, the value appears to be 2.75. */ * reported, the value appears to be 2.75. */
float kpa = (float)raw[7]*2.75; float kpa = (float)raw[7] * 2.75;
int temp_f = raw[8]; int temp_f = raw[8];
int temp_c = (temp_f-32)*5/9; /* Convert Fahrenheit to Celsius. */ int temp_c = (temp_f - 32) * 5 / 9; /* Convert Fahrenheit to Celsius. */
snprintf(info->name,sizeof(info->name),"%s","Schrader EG53MA4 TPMS"); snprintf(info->name, sizeof(info->name), "%s", "Schrader EG53MA4 TPMS");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X", snprintf(
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5], info->raw,
raw[6],raw[7],raw[8],raw[9]); sizeof(info->raw),
snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X", "%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[4],raw[5],raw[6]); /* Only 28 bits of ID, not 32. */ raw[0],
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f kpa", (double)kpa); raw[1],
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp_c); raw[2],
raw[3],
raw[4],
raw[5],
raw[6],
raw[7],
raw[8],
raw[9]);
snprintf(
info->info1,
sizeof(info->info1),
"Tire ID %02X%02X%02X",
raw[4],
raw[5],
raw[6]); /* Only 28 bits of ID, not 32. */
snprintf(info->info2, sizeof(info->info2), "Pressure %.2f kpa", (double)kpa);
snprintf(info->info3, sizeof(info->info3), "Temperature %d C", temp_c);
return true; return true;
} }
ProtoViewDecoder SchraderEG53MA4TPMSDecoder = { ProtoViewDecoder SchraderEG53MA4TPMSDecoder = {"Schrader EG53MA4 TPMS", decode};
"Schrader EG53MA4 TPMS", decode
};

View file

@ -24,40 +24,33 @@
#include "../../app.h" #include "../../app.h"
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) { static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info) {
if(numbits - 6 < 64 * 2)
if (numbits-6 < 64*2) return false; /* Ask for 64 bit of data (each bit return false; /* Ask for 64 bit of data (each bit
is two symbols in the bitmap). */ is two symbols in the bitmap). */
char *sync[] = { char* sync[] = {"00111100", "001111100", "00111101", "001111101", NULL};
"00111100",
"001111100",
"00111101",
"001111101",
NULL
};
int j; int j;
uint32_t off = 0; uint32_t off = 0;
for (j = 0; sync[j]; j++) { for(j = 0; sync[j]; j++) {
off = bitmap_seek_bits(bits,numbytes,0,numbits,sync[j]); off = bitmap_seek_bits(bits, numbytes, 0, numbits, sync[j]);
if (off != BITMAP_SEEK_NOT_FOUND) { if(off != BITMAP_SEEK_NOT_FOUND) {
info->start_off = off; info->start_off = off;
off += strlen(sync[j])-2; off += strlen(sync[j]) - 2;
break; break;
} }
} }
if (off == BITMAP_SEEK_NOT_FOUND) return false; if(off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Toyota TPMS sync[%s] found", sync[j]); FURI_LOG_E(TAG, "Toyota TPMS sync[%s] found", sync[j]);
uint8_t raw[9]; uint8_t raw[9];
uint32_t decoded = uint32_t decoded = convert_from_diff_manchester(raw, sizeof(raw), bits, numbytes, off, true);
convert_from_diff_manchester(raw,sizeof(raw),bits,numbytes,off,true);
FURI_LOG_E(TAG, "Toyota TPMS decoded bits: %lu", decoded); FURI_LOG_E(TAG, "Toyota TPMS decoded bits: %lu", decoded);
if (decoded < 8*9) return false; /* Require the full 8 bytes. */ if(decoded < 8 * 9) return false; /* Require the full 8 bytes. */
if (crc8(raw,8,0x80,7) != raw[8]) return false; /* Require sane CRC. */ if(crc8(raw, 8, 0x80, 7) != raw[8]) return false; /* Require sane CRC. */
/* We detected a valid signal. However now info->start_off is actually /* We detected a valid signal. However now info->start_off is actually
* pointing to the sync part, not the preamble of alternating 0 and 1. * pointing to the sync part, not the preamble of alternating 0 and 1.
@ -65,27 +58,41 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
* for the decoder itself to fix the signal if neeeded, so that its * for the decoder itself to fix the signal if neeeded, so that its
* logical representation will be more accurate and better to save * logical representation will be more accurate and better to save
* and retransmit. */ * and retransmit. */
if (info->start_off >= 12) { if(info->start_off >= 12) {
info->start_off -= 12; info->start_off -= 12;
bitmap_set_pattern(bits,numbytes,info->start_off,"010101010101"); bitmap_set_pattern(bits, numbytes, info->start_off, "010101010101");
} }
info->pulses_count = (off+8*9*2) - info->start_off; info->pulses_count = (off + 8 * 9 * 2) - info->start_off;
float kpa = (float)((raw[4]&0x7f)<<1 | raw[5]>>7) * 0.25 - 7; float kpa = (float)((raw[4] & 0x7f) << 1 | raw[5] >> 7) * 0.25 - 7;
int temp = ((raw[5]&0x7f)<<1 | raw[6]>>7) - 40; int temp = ((raw[5] & 0x7f) << 1 | raw[6] >> 7) - 40;
snprintf(info->name,sizeof(info->name),"%s","Toyota TPMS"); snprintf(info->name, sizeof(info->name), "%s", "Toyota TPMS");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X", snprintf(
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5], info->raw,
raw[6],raw[7],raw[8]); sizeof(info->raw),
snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X%02X", "%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3]); raw[0],
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f psi", (double)kpa); raw[1],
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp); raw[2],
raw[3],
raw[4],
raw[5],
raw[6],
raw[7],
raw[8]);
snprintf(
info->info1,
sizeof(info->info1),
"Tire ID %02X%02X%02X%02X",
raw[0],
raw[1],
raw[2],
raw[3]);
snprintf(info->info2, sizeof(info->info2), "Pressure %.2f psi", (double)kpa);
snprintf(info->info3, sizeof(info->info3), "Temperature %d C", temp);
return true; return true;
} }
ProtoViewDecoder ToyotaTPMSDecoder = { ProtoViewDecoder ToyotaTPMSDecoder = {"Toyota TPMS", decode};
"Toyota TPMS", decode
};

View file

@ -3,7 +3,7 @@
#include "app.h" #include "app.h"
bool decode_signal(RawSamplesBuffer *s, uint64_t len, ProtoViewMsgInfo *info); bool decode_signal(RawSamplesBuffer* s, uint64_t len, ProtoViewMsgInfo* info);
/* ============================================================================= /* =============================================================================
* Raw signal detection * Raw signal detection
@ -16,7 +16,7 @@ uint32_t duration_delta(uint32_t a, uint32_t b) {
} }
/* Reset the current signal, so that a new one can be detected. */ /* Reset the current signal, so that a new one can be detected. */
void reset_current_signal(ProtoViewApp *app) { void reset_current_signal(ProtoViewApp* app) {
app->signal_bestlen = 0; app->signal_bestlen = 0;
app->signal_offset = 0; app->signal_offset = 0;
app->signal_decoded = false; app->signal_decoded = false;
@ -39,47 +39,47 @@ void reset_current_signal(ProtoViewApp *app) {
* For instance Oregon2 sensors, in the case of protocol 2.1 will send * For instance Oregon2 sensors, in the case of protocol 2.1 will send
* pulses of ~400us (RF on) VS ~580us (RF off). */ * pulses of ~400us (RF on) VS ~580us (RF off). */
#define SEARCH_CLASSES 3 #define SEARCH_CLASSES 3
uint32_t search_coherent_signal(RawSamplesBuffer *s, uint32_t idx) { uint32_t search_coherent_signal(RawSamplesBuffer* s, uint32_t idx) {
struct { struct {
uint32_t dur[2]; /* dur[0] = low, dur[1] = high */ uint32_t dur[2]; /* dur[0] = low, dur[1] = high */
uint32_t count[2]; /* Associated observed frequency. */ uint32_t count[2]; /* Associated observed frequency. */
} classes[SEARCH_CLASSES]; } classes[SEARCH_CLASSES];
memset(classes,0,sizeof(classes)); memset(classes, 0, sizeof(classes));
uint32_t minlen = 30, maxlen = 4000; /* Depends on data rate, here we uint32_t minlen = 30, maxlen = 4000; /* Depends on data rate, here we
allow for high and low. */ allow for high and low. */
uint32_t len = 0; /* Observed len of coherent samples. */ uint32_t len = 0; /* Observed len of coherent samples. */
s->short_pulse_dur = 0; s->short_pulse_dur = 0;
for (uint32_t j = idx; j < idx+500; j++) { for(uint32_t j = idx; j < idx + 500; j++) {
bool level; bool level;
uint32_t dur; uint32_t dur;
raw_samples_get(s, j, &level, &dur); raw_samples_get(s, j, &level, &dur);
if (dur < minlen || dur > maxlen) break; /* return. */ if(dur < minlen || dur > maxlen) break; /* return. */
/* Let's see if it matches a class we already have or if we /* Let's see if it matches a class we already have or if we
* can populate a new (yet empty) class. */ * can populate a new (yet empty) class. */
uint32_t k; uint32_t k;
for (k = 0; k < SEARCH_CLASSES; k++) { for(k = 0; k < SEARCH_CLASSES; k++) {
if (classes[k].count[level] == 0) { if(classes[k].count[level] == 0) {
classes[k].dur[level] = dur; classes[k].dur[level] = dur;
classes[k].count[level] = 1; classes[k].count[level] = 1;
break; /* Sample accepted. */ break; /* Sample accepted. */
} else { } else {
uint32_t classavg = classes[k].dur[level]; uint32_t classavg = classes[k].dur[level];
uint32_t count = classes[k].count[level]; uint32_t count = classes[k].count[level];
uint32_t delta = duration_delta(dur,classavg); uint32_t delta = duration_delta(dur, classavg);
/* Is the difference in duration between this signal and /* Is the difference in duration between this signal and
* the class we are inspecting less than a given percentage? * the class we are inspecting less than a given percentage?
* If so, accept this signal. */ * If so, accept this signal. */
if (delta < classavg/5) { /* 100%/5 = 20%. */ if(delta < classavg / 5) { /* 100%/5 = 20%. */
/* It is useful to compute the average of the class /* It is useful to compute the average of the class
* we are observing. We know how many samples we got so * we are observing. We know how many samples we got so
* far, so we can recompute the average easily. * far, so we can recompute the average easily.
* By always having a better estimate of the pulse len * By always having a better estimate of the pulse len
* we can avoid missing next samples in case the first * we can avoid missing next samples in case the first
* observed samples are too off. */ * observed samples are too off. */
classavg = ((classavg * count) + dur) / (count+1); classavg = ((classavg * count) + dur) / (count + 1);
classes[k].dur[level] = classavg; classes[k].dur[level] = classavg;
classes[k].count[level]++; classes[k].count[level]++;
break; /* Sample accepted. */ break; /* Sample accepted. */
@ -87,7 +87,7 @@ uint32_t search_coherent_signal(RawSamplesBuffer *s, uint32_t idx) {
} }
} }
if (k == SEARCH_CLASSES) break; /* No match, return. */ if(k == SEARCH_CLASSES) break; /* No match, return. */
/* If we are here, we accepted this sample. Try with the next /* If we are here, we accepted this sample. Try with the next
* one. */ * one. */
@ -97,14 +97,12 @@ uint32_t search_coherent_signal(RawSamplesBuffer *s, uint32_t idx) {
/* Update the buffer setting the shortest pulse we found /* Update the buffer setting the shortest pulse we found
* among the three classes. This will be used when scaling * among the three classes. This will be used when scaling
* for visualization. */ * for visualization. */
uint32_t short_dur[2] = {0,0}; uint32_t short_dur[2] = {0, 0};
for (int j = 0; j < SEARCH_CLASSES; j++) { for(int j = 0; j < SEARCH_CLASSES; j++) {
for (int level = 0; level < 2; level++) { for(int level = 0; level < 2; level++) {
if (classes[j].dur[level] == 0) continue; if(classes[j].dur[level] == 0) continue;
if (classes[j].count[level] < 3) continue; if(classes[j].count[level] < 3) continue;
if (short_dur[level] == 0 || if(short_dur[level] == 0 || short_dur[level] > classes[j].dur[level]) {
short_dur[level] > classes[j].dur[level])
{
short_dur[level] = classes[j].dur[level]; short_dur[level] = classes[j].dur[level];
} }
} }
@ -113,9 +111,9 @@ uint32_t search_coherent_signal(RawSamplesBuffer *s, uint32_t idx) {
/* Use the average between high and low short pulses duration. /* Use the average between high and low short pulses duration.
* Often they are a bit different, and using the average is more robust * Often they are a bit different, and using the average is more robust
* when we do decoding sampling at short_pulse_dur intervals. */ * when we do decoding sampling at short_pulse_dur intervals. */
if (short_dur[0] == 0) short_dur[0] = short_dur[1]; if(short_dur[0] == 0) short_dur[0] = short_dur[1];
if (short_dur[1] == 0) short_dur[1] = short_dur[0]; if(short_dur[1] == 0) short_dur[1] = short_dur[0];
s->short_pulse_dur = (short_dur[0]+short_dur[1])/2; s->short_pulse_dur = (short_dur[0] + short_dur[1]) / 2;
return len; return len;
} }
@ -124,60 +122,62 @@ uint32_t search_coherent_signal(RawSamplesBuffer *s, uint32_t idx) {
* in order to find a coherent signal. If a signal that does not appear to * 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 * be just noise is found, it is set in DetectedSamples global signal
* buffer, that is what is rendered on the screen. */ * buffer, that is what is rendered on the screen. */
void scan_for_signal(ProtoViewApp *app) { void scan_for_signal(ProtoViewApp* app) {
/* We need to work on a copy: the RawSamples buffer is populated /* We need to work on a copy: the RawSamples buffer is populated
* by the background thread receiving data. */ * by the background thread receiving data. */
RawSamplesBuffer *copy = raw_samples_alloc(); RawSamplesBuffer* copy = raw_samples_alloc();
raw_samples_copy(copy,RawSamples); raw_samples_copy(copy, RawSamples);
/* Try to seek on data that looks to have a regular high low high low /* Try to seek on data that looks to have a regular high low high low
* pattern. */ * pattern. */
uint32_t minlen = 18; /* Min run of coherent samples. With less uint32_t minlen = 18; /* Min run of coherent samples. With less
than a few samples it's very easy to than a few samples it's very easy to
mistake noise for signal. */ mistake noise for signal. */
uint32_t i = 0; uint32_t i = 0;
while (i < copy->total-1) { while(i < copy->total - 1) {
uint32_t thislen = search_coherent_signal(copy,i); uint32_t thislen = search_coherent_signal(copy, i);
/* For messages that are long enough, attempt decoding. */ /* For messages that are long enough, attempt decoding. */
if (thislen > minlen) { if(thislen > minlen) {
/* Allocate the message information that some decoder may /* Allocate the message information that some decoder may
* fill, in case it is able to decode a message. */ * fill, in case it is able to decode a message. */
ProtoViewMsgInfo *info = malloc(sizeof(ProtoViewMsgInfo)); ProtoViewMsgInfo* info = malloc(sizeof(ProtoViewMsgInfo));
init_msg_info(info,app); init_msg_info(info, app);
info->short_pulse_dur = copy->short_pulse_dur; info->short_pulse_dur = copy->short_pulse_dur;
uint32_t saved_idx = copy->idx; /* Save index, see later. */ uint32_t saved_idx = copy->idx; /* Save index, see later. */
/* decode_signal() expects the detected signal to start /* decode_signal() expects the detected signal to start
* from index zero .*/ * from index zero .*/
raw_samples_center(copy,i); raw_samples_center(copy, i);
bool decoded = decode_signal(copy,thislen,info); bool decoded = decode_signal(copy, thislen, info);
copy->idx = saved_idx; /* Restore the index as we are scanning copy->idx = saved_idx; /* Restore the index as we are scanning
the signal in the loop. */ the signal in the loop. */
/* Accept this signal as the new signal if either it's longer /* Accept this signal as the new signal if either it's longer
* than the previous undecoded one, or the previous one was * than the previous undecoded one, or the previous one was
* unknown and this is decoded. */ * unknown and this is decoded. */
if ((thislen > app->signal_bestlen && app->signal_decoded == false) if((thislen > app->signal_bestlen && app->signal_decoded == false) ||
|| (app->signal_decoded == false && decoded)) (app->signal_decoded == false && decoded)) {
{
free_msg_info(app->msg_info); free_msg_info(app->msg_info);
app->msg_info = info; app->msg_info = info;
app->signal_bestlen = thislen; app->signal_bestlen = thislen;
app->signal_decoded = decoded; app->signal_decoded = decoded;
raw_samples_copy(DetectedSamples,copy); raw_samples_copy(DetectedSamples, copy);
raw_samples_center(DetectedSamples,i); raw_samples_center(DetectedSamples, i);
FURI_LOG_E(TAG, "===> Displayed sample updated (%d samples %lu us)", FURI_LOG_E(
(int)thislen, DetectedSamples->short_pulse_dur); TAG,
"===> Displayed sample updated (%d samples %lu us)",
(int)thislen,
DetectedSamples->short_pulse_dur);
/* Adjust raw view scale if the signal has an high /* Adjust raw view scale if the signal has an high
* data rate. */ * data rate. */
if (DetectedSamples->short_pulse_dur < 75) if(DetectedSamples->short_pulse_dur < 75)
app->us_scale = 10; app->us_scale = 10;
else if (DetectedSamples->short_pulse_dur < 145) else if(DetectedSamples->short_pulse_dur < 145)
app->us_scale = 30; app->us_scale = 30;
} else { } else {
/* If the structure was not filled, discard it. Otherwise /* If the structure was not filled, discard it. Otherwise
@ -206,38 +206,42 @@ void scan_for_signal(ProtoViewApp *app) {
/* Set the 'bitpos' bit to value 'val', in the specified bitmap /* Set the 'bitpos' bit to value 'val', in the specified bitmap
* 'b' of len 'blen'. * 'b' of len 'blen'.
* Out of range bits will silently be discarded. */ * Out of range bits will silently be discarded. */
void bitmap_set(uint8_t *b, uint32_t blen, uint32_t bitpos, bool val) { void bitmap_set(uint8_t* b, uint32_t blen, uint32_t bitpos, bool val) {
uint32_t byte = bitpos/8; uint32_t byte = bitpos / 8;
uint32_t bit = 7-(bitpos&7); uint32_t bit = 7 - (bitpos & 7);
if (byte >= blen) return; if(byte >= blen) return;
if (val) if(val)
b[byte] |= 1<<bit; b[byte] |= 1 << bit;
else else
b[byte] &= ~(1<<bit); b[byte] &= ~(1 << bit);
} }
/* Get the bit 'bitpos' of the bitmap 'b' of 'blen' bytes. /* Get the bit 'bitpos' of the bitmap 'b' of 'blen' bytes.
* Out of range bits return false (not bit set). */ * Out of range bits return false (not bit set). */
bool bitmap_get(uint8_t *b, uint32_t blen, uint32_t bitpos) { bool bitmap_get(uint8_t* b, uint32_t blen, uint32_t bitpos) {
uint32_t byte = bitpos/8; uint32_t byte = bitpos / 8;
uint32_t bit = 7-(bitpos&7); uint32_t bit = 7 - (bitpos & 7);
if (byte >= blen) return 0; if(byte >= blen) return 0;
return (b[byte] & (1<<bit)) != 0; return (b[byte] & (1 << bit)) != 0;
} }
/* Copy 'count' bits from the bitmap 's' of 'slen' total bytes, to the /* Copy 'count' bits from the bitmap 's' of 'slen' total bytes, to the
* bitmap 'd' of 'dlen' total bytes. The bits are copied starting from * bitmap 'd' of 'dlen' total bytes. The bits are copied starting from
* offset 'soff' of the source bitmap to the offset 'doff' of the * offset 'soff' of the source bitmap to the offset 'doff' of the
* destination bitmap. */ * destination bitmap. */
void bitmap_copy(uint8_t *d, uint32_t dlen, uint32_t doff, void bitmap_copy(
uint8_t *s, uint32_t slen, uint32_t soff, uint8_t* d,
uint32_t count) uint32_t dlen,
{ uint32_t doff,
uint8_t* s,
uint32_t slen,
uint32_t soff,
uint32_t count) {
/* If we are byte-aligned in both source and destination, use a fast /* If we are byte-aligned in both source and destination, use a fast
* path for the number of bytes we can consume this way. */ * path for the number of bytes we can consume this way. */
if ((doff & 7) == 0 && (soff & 7) == 0) { if((doff & 7) == 0 && (soff & 7) == 0) {
uint32_t didx = doff/8; uint32_t didx = doff / 8;
uint32_t sidx = soff/8; uint32_t sidx = soff / 8;
while(count > 8 && didx < dlen && sidx < slen) { while(count > 8 && didx < dlen && sidx < slen) {
d[didx++] = s[sidx++]; d[didx++] = s[sidx++];
count -= 8; count -= 8;
@ -250,9 +254,9 @@ void bitmap_copy(uint8_t *d, uint32_t dlen, uint32_t doff,
/* Copy the bits needed to reach an offset where we can copy /* Copy the bits needed to reach an offset where we can copy
* two half bytes of src to a full byte of destination. */ * two half bytes of src to a full byte of destination. */
while(count > 8 && (doff&7) != 0) { while(count > 8 && (doff & 7) != 0) {
bool bit = bitmap_get(s,slen,soff++); bool bit = bitmap_get(s, slen, soff++);
bitmap_set(d,dlen,doff++,bit); bitmap_set(d, dlen, doff++, bit);
count--; count--;
} }
@ -295,13 +299,12 @@ void bitmap_copy(uint8_t *d, uint32_t dlen, uint32_t doff,
* src[2] << 5, that is "WORLDS!!" >> 5 = ".....WOR" * src[2] << 5, that is "WORLDS!!" >> 5 = ".....WOR"
* That is "HELLOWOR" * That is "HELLOWOR"
*/ */
if (count > 8) { if(count > 8) {
uint8_t skew = soff % 8; /* Don't worry, compiler will optimize. */ uint8_t skew = soff % 8; /* Don't worry, compiler will optimize. */
uint32_t didx = doff/8; uint32_t didx = doff / 8;
uint32_t sidx = soff/8; uint32_t sidx = soff / 8;
while(count > 8 && didx < dlen && sidx < slen) { while(count > 8 && didx < dlen && sidx < slen) {
d[didx] = ((s[sidx] << skew) | d[didx] = ((s[sidx] << skew) | (s[sidx + 1] >> (8 - skew)));
(s[sidx+1] >> (8-skew)));
sidx++; sidx++;
didx++; didx++;
soff += 8; soff += 8;
@ -313,8 +316,8 @@ void bitmap_copy(uint8_t *d, uint32_t dlen, uint32_t doff,
/* Here count is guaranteed to be < 8. /* Here count is guaranteed to be < 8.
* Copy the final bits bit by bit. */ * Copy the final bits bit by bit. */
while(count) { while(count) {
bool bit = bitmap_get(s,slen,soff++); bool bit = bitmap_get(s, slen, soff++);
bitmap_set(d,dlen,doff++,bit); bitmap_set(d, dlen, doff++, bit);
count--; count--;
} }
} }
@ -322,15 +325,15 @@ void bitmap_copy(uint8_t *d, uint32_t dlen, uint32_t doff,
/* We decode bits assuming the first bit we receive is the MSB /* We decode bits assuming the first bit we receive is the MSB
* (see bitmap_set/get functions). Certain devices send data * (see bitmap_set/get functions). Certain devices send data
* encoded in the reverse way. */ * encoded in the reverse way. */
void bitmap_reverse_bytes(uint8_t *p, uint32_t len) { void bitmap_reverse_bytes(uint8_t* p, uint32_t len) {
for (uint32_t j = 0; j < len; j++) { for(uint32_t j = 0; j < len; j++) {
uint32_t b = p[j]; uint32_t b = p[j];
/* Step 1: swap the two nibbles: 12345678 -> 56781234 */ /* Step 1: swap the two nibbles: 12345678 -> 56781234 */
b = (b&0xf0)>>4 | (b&0x0f)<<4; b = (b & 0xf0) >> 4 | (b & 0x0f) << 4;
/* Step 2: swap adjacent pairs : 56781234 -> 78563412 */ /* Step 2: swap adjacent pairs : 56781234 -> 78563412 */
b = (b&0xcc)>>2 | (b&0x33)<<2; b = (b & 0xcc) >> 2 | (b & 0x33) << 2;
/* Step 3: swap adjacent bits : 78563412 -> 87654321 */ /* Step 3: swap adjacent bits : 78563412 -> 87654321 */
b = (b&0xaa)>>1 | (b&0x55)<<1; b = (b & 0xaa) >> 1 | (b & 0x55) << 1;
p[j] = b; p[j] = b;
} }
} }
@ -338,10 +341,10 @@ void bitmap_reverse_bytes(uint8_t *p, uint32_t len) {
/* Return true if the specified sequence of bits, provided as a string in the /* Return true if the specified sequence of bits, provided as a string in the
* form "11010110..." is found in the 'b' bitmap of 'blen' bits at 'bitpos' * form "11010110..." is found in the 'b' bitmap of 'blen' bits at 'bitpos'
* position. */ * position. */
bool bitmap_match_bits(uint8_t *b, uint32_t blen, uint32_t bitpos, const char *bits) { bool bitmap_match_bits(uint8_t* b, uint32_t blen, uint32_t bitpos, const char* bits) {
for (size_t j = 0; bits[j]; j++) { for(size_t j = 0; bits[j]; j++) {
bool expected = (bits[j] == '1') ? true : false; bool expected = (bits[j] == '1') ? true : false;
if (bitmap_get(b,blen,bitpos+j) != expected) return false; if(bitmap_get(b, blen, bitpos + j) != expected) return false;
} }
return true; return true;
} }
@ -354,12 +357,17 @@ bool bitmap_match_bits(uint8_t *b, uint32_t blen, uint32_t bitpos, const char *b
* Note: there are better algorithms, such as Boyer-Moore. Here we hope that * Note: there are better algorithms, such as Boyer-Moore. Here we hope that
* for the kind of patterns we search we'll have a lot of early stops so * for the kind of patterns we search we'll have a lot of early stops so
* we use a vanilla approach. */ * we use a vanilla approach. */
uint32_t bitmap_seek_bits(uint8_t *b, uint32_t blen, uint32_t startpos, uint32_t maxbits, const char *bits) { uint32_t bitmap_seek_bits(
uint32_t endpos = startpos+blen*8; uint8_t* b,
uint32_t end2 = startpos+maxbits; uint32_t blen,
if (end2 < endpos) endpos = end2; uint32_t startpos,
for (uint32_t j = startpos; j < endpos; j++) uint32_t maxbits,
if (bitmap_match_bits(b,blen,j,bits)) return j; const char* bits) {
uint32_t endpos = startpos + blen * 8;
uint32_t end2 = startpos + maxbits;
if(end2 < endpos) endpos = end2;
for(uint32_t j = startpos; j < endpos; j++)
if(bitmap_match_bits(b, blen, j, bits)) return j;
return BITMAP_SEEK_NOT_FOUND; return BITMAP_SEEK_NOT_FOUND;
} }
@ -370,10 +378,10 @@ uint32_t bitmap_seek_bits(uint8_t *b, uint32_t blen, uint32_t startpos, uint32_t
* This function is useful in order to set the test vectors in the protocol * This function is useful in order to set the test vectors in the protocol
* decoders, to see if the decoding works regardless of the fact we are able * decoders, to see if the decoding works regardless of the fact we are able
* to actually receive a given signal. */ * to actually receive a given signal. */
void bitmap_set_pattern(uint8_t *b, uint32_t blen, uint32_t off, const char *pat) { void bitmap_set_pattern(uint8_t* b, uint32_t blen, uint32_t off, const char* pat) {
uint32_t i = 0; uint32_t i = 0;
while(pat[i]) { while(pat[i]) {
bitmap_set(b,blen,i+off,pat[i] == '1'); bitmap_set(b, blen, i + off, pat[i] == '1');
i++; i++;
} }
} }
@ -405,31 +413,36 @@ void bitmap_set_pattern(uint8_t *b, uint32_t blen, uint32_t off, const char *pat
* bits set into the buffer 'b'. The 'rate' argument, in microseconds, is * bits set into the buffer 'b'. The 'rate' argument, in microseconds, is
* the detected short-pulse duration. We expect the line code to be * the detected short-pulse duration. We expect the line code to be
* meaningful when interpreted at multiples of 'rate'. */ * meaningful when interpreted at multiples of 'rate'. */
uint32_t convert_signal_to_bits(uint8_t *b, uint32_t blen, RawSamplesBuffer *s, uint32_t idx, uint32_t count, uint32_t rate) { uint32_t convert_signal_to_bits(
if (rate == 0) return 0; /* We can't perform the conversion. */ uint8_t* b,
uint32_t blen,
RawSamplesBuffer* s,
uint32_t idx,
uint32_t count,
uint32_t rate) {
if(rate == 0) return 0; /* We can't perform the conversion. */
uint32_t bitpos = 0; uint32_t bitpos = 0;
for (uint32_t j = 0; j < count; j++) { for(uint32_t j = 0; j < count; j++) {
uint32_t dur; uint32_t dur;
bool level; bool level;
raw_samples_get(s, j+idx, &level, &dur); raw_samples_get(s, j + idx, &level, &dur);
uint32_t numbits = dur / rate; /* full bits that surely fit. */ uint32_t numbits = dur / rate; /* full bits that surely fit. */
uint32_t rest = dur % rate; /* How much we are left with. */ uint32_t rest = dur % rate; /* How much we are left with. */
if (rest > rate/2) numbits++; /* There is another one. */ if(rest > rate / 2) numbits++; /* There is another one. */
/* Limit how much a single sample can spawn. There are likely no /* Limit how much a single sample can spawn. There are likely no
* protocols doing such long pulses when the rate is low. */ * protocols doing such long pulses when the rate is low. */
if (numbits > 1024) numbits = 1024; if(numbits > 1024) numbits = 1024;
if (0) /* Super verbose, so not under the DEBUG_MSG define. */ if(0) /* Super verbose, so not under the DEBUG_MSG define. */
FURI_LOG_E(TAG, "%lu converted into %lu (%d) bits", FURI_LOG_E(TAG, "%lu converted into %lu (%d) bits", dur, numbits, (int)level);
dur,numbits,(int)level);
/* If the signal is too short, let's claim it an interference /* If the signal is too short, let's claim it an interference
* and ignore it completely. */ * and ignore it completely. */
if (numbits == 0) continue; if(numbits == 0) continue;
while(numbits--) bitmap_set(b,blen,bitpos++,level); while(numbits--) bitmap_set(b, blen, bitpos++, level);
} }
return bitpos; return bitpos;
} }
@ -446,23 +459,29 @@ uint32_t convert_signal_to_bits(uint8_t *b, uint32_t blen, RawSamplesBuffer *s,
* specified in bytes by the caller, via the 'len' parameters). * specified in bytes by the caller, via the 'len' parameters).
* *
* The decoding starts at the specified offset (in bits) 'off'. */ * 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 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. */ uint32_t decoded = 0; /* Number of bits extracted. */
len *= 8; /* Convert bytes to bits. */ len *= 8; /* Convert bytes to bits. */
while(off < len) { while(off < len) {
bool bitval; bool bitval;
if (bitmap_match_bits(bits,len,off,zero_pattern)) { if(bitmap_match_bits(bits, len, off, zero_pattern)) {
bitval = false; bitval = false;
off += strlen(zero_pattern); off += strlen(zero_pattern);
} else if (bitmap_match_bits(bits,len,off,one_pattern)) { } else if(bitmap_match_bits(bits, len, off, one_pattern)) {
bitval = true; bitval = true;
off += strlen(one_pattern); off += strlen(one_pattern);
} else { } else {
break; break;
} }
bitmap_set(buf,buflen,decoded++,bitval); bitmap_set(buf, buflen, decoded++, bitval);
if (decoded/8 == buflen) break; /* No space left on target buffer. */ if(decoded / 8 == buflen) break; /* No space left on target buffer. */
} }
return decoded; return decoded;
} }
@ -473,17 +492,22 @@ uint32_t convert_from_line_code(uint8_t *buf, uint64_t buflen, uint8_t *bits, ui
* in differential codings the next bits depend on the previous one. * in differential codings the next bits depend on the previous one.
* *
* Parameters and return values are like convert_from_line_code(). */ * Parameters and return values are like convert_from_line_code(). */
uint32_t convert_from_diff_manchester(uint8_t *buf, uint64_t buflen, uint8_t *bits, uint32_t len, uint32_t off, bool previous) uint32_t convert_from_diff_manchester(
{ uint8_t* buf,
uint64_t buflen,
uint8_t* bits,
uint32_t len,
uint32_t off,
bool previous) {
uint32_t decoded = 0; uint32_t decoded = 0;
len *= 8; /* Conver to bits. */ len *= 8; /* Conver to bits. */
for (uint32_t j = off; j < len; j += 2) { for(uint32_t j = off; j < len; j += 2) {
bool b0 = bitmap_get(bits,len,j); bool b0 = bitmap_get(bits, len, j);
bool b1 = bitmap_get(bits,len,j+1); bool b1 = bitmap_get(bits, len, j + 1);
if (b0 == previous) break; /* Each new bit must switch value. */ if(b0 == previous) break; /* Each new bit must switch value. */
bitmap_set(buf,buflen,decoded++,b0 == b1); bitmap_set(buf, buflen, decoded++, b0 == b1);
previous = b1; previous = b1;
if (decoded/8 == buflen) break; /* No space left on target buffer. */ if(decoded / 8 == buflen) break; /* No space left on target buffer. */
} }
return decoded; return decoded;
} }
@ -501,31 +525,30 @@ extern ProtoViewDecoder CitroenTPMSDecoder;
extern ProtoViewDecoder FordTPMSDecoder; extern ProtoViewDecoder FordTPMSDecoder;
extern ProtoViewDecoder KeeloqDecoder; extern ProtoViewDecoder KeeloqDecoder;
ProtoViewDecoder *Decoders[] = { ProtoViewDecoder* Decoders[] = {
&Oregon2Decoder, /* Oregon sensors v2.1 protocol. */ &Oregon2Decoder, /* Oregon sensors v2.1 protocol. */
&B4B1Decoder, /* PT, SC, ... 24 bits remotes. */ &B4B1Decoder, /* PT, SC, ... 24 bits remotes. */
&RenaultTPMSDecoder, /* Renault TPMS. */ &RenaultTPMSDecoder, /* Renault TPMS. */
&ToyotaTPMSDecoder, /* Toyota TPMS. */ &ToyotaTPMSDecoder, /* Toyota TPMS. */
&SchraderTPMSDecoder, /* Schrader TPMS. */ &SchraderTPMSDecoder, /* Schrader TPMS. */
&SchraderEG53MA4TPMSDecoder, /* Schrader EG53MA4 TPMS. */ &SchraderEG53MA4TPMSDecoder, /* Schrader EG53MA4 TPMS. */
&CitroenTPMSDecoder, /* Citroen TPMS. */ &CitroenTPMSDecoder, /* Citroen TPMS. */
&FordTPMSDecoder, /* Ford TPMS. */ &FordTPMSDecoder, /* Ford TPMS. */
&KeeloqDecoder, /* Keeloq remote. */ &KeeloqDecoder, /* Keeloq remote. */
NULL NULL};
};
/* Free the message info and allocated data. */ /* Free the message info and allocated data. */
void free_msg_info(ProtoViewMsgInfo *i) { void free_msg_info(ProtoViewMsgInfo* i) {
if (i == NULL) return; if(i == NULL) return;
free(i->bits); free(i->bits);
free(i); free(i);
} }
/* Reset the message info structure before passing it to the decoding /* Reset the message info structure before passing it to the decoding
* functions. */ * functions. */
void init_msg_info(ProtoViewMsgInfo *i, ProtoViewApp *app) { void init_msg_info(ProtoViewMsgInfo* i, ProtoViewApp* app) {
UNUSED(app); UNUSED(app);
memset(i,0,sizeof(ProtoViewMsgInfo)); memset(i, 0, sizeof(ProtoViewMsgInfo));
i->bits = NULL; i->bits = NULL;
} }
@ -533,23 +556,29 @@ void init_msg_info(ProtoViewMsgInfo *i, ProtoViewApp *app) {
* to a bitstream, and the calls the protocol specific functions for * to a bitstream, and the calls the protocol specific functions for
* decoding. If the signal was decoded correctly by some protocol, true * decoding. If the signal was decoded correctly by some protocol, true
* is returned. Otherwise false is returned. */ * is returned. Otherwise false is returned. */
bool decode_signal(RawSamplesBuffer *s, uint64_t len, ProtoViewMsgInfo *info) { bool decode_signal(RawSamplesBuffer* s, uint64_t len, ProtoViewMsgInfo* info) {
uint32_t bitmap_bits_size = 4096*8; uint32_t bitmap_bits_size = 4096 * 8;
uint32_t bitmap_size = bitmap_bits_size/8; uint32_t bitmap_size = bitmap_bits_size / 8;
/* We call the decoders with an offset a few samples before the actual /* We call the decoders with an offset a few samples before the actual
* signal detected and for a len of a few bits after its end. */ * signal detected and for a len of a few bits after its end. */
uint32_t before_samples = 32; uint32_t before_samples = 32;
uint32_t after_samples = 100; uint32_t after_samples = 100;
uint8_t *bitmap = malloc(bitmap_size); uint8_t* bitmap = malloc(bitmap_size);
uint32_t bits = convert_signal_to_bits(bitmap,bitmap_size,s,-before_samples,len+before_samples+after_samples,s->short_pulse_dur); uint32_t bits = convert_signal_to_bits(
bitmap,
bitmap_size,
s,
-before_samples,
len + before_samples + after_samples,
s->short_pulse_dur);
if (DEBUG_MSG) { /* Useful for debugging purposes. Don't remove. */ if(DEBUG_MSG) { /* Useful for debugging purposes. Don't remove. */
char *str = malloc(1024); char* str = malloc(1024);
uint32_t j; uint32_t j;
for (j = 0; j < bits && j < 1023; j++) { for(j = 0; j < bits && j < 1023; j++) {
str[j] = bitmap_get(bitmap,bitmap_size,j) ? '1' : '0'; str[j] = bitmap_get(bitmap, bitmap_size, j) ? '1' : '0';
} }
str[j] = 0; str[j] = 0;
FURI_LOG_E(TAG, "%lu bits sampled: %s", bits, str); FURI_LOG_E(TAG, "%lu bits sampled: %s", bits, str);
@ -562,30 +591,40 @@ bool decode_signal(RawSamplesBuffer *s, uint64_t len, ProtoViewMsgInfo *info) {
bool decoded = false; bool decoded = false;
while(Decoders[j]) { while(Decoders[j]) {
uint32_t start_time = furi_get_tick(); uint32_t start_time = furi_get_tick();
decoded = Decoders[j]->decode(bitmap,bitmap_size,bits,info); decoded = Decoders[j]->decode(bitmap, bitmap_size, bits, info);
uint32_t delta = furi_get_tick() - start_time; uint32_t delta = furi_get_tick() - start_time;
FURI_LOG_E(TAG, "Decoder %s took %lu ms", FURI_LOG_E(TAG, "Decoder %s took %lu ms", Decoders[j]->name, (unsigned long)delta);
Decoders[j]->name, (unsigned long)delta); if(decoded) break;
if (decoded) break;
j++; j++;
} }
if (!decoded) { if(!decoded) {
FURI_LOG_E(TAG, "No decoding possible"); FURI_LOG_E(TAG, "No decoding possible");
} else { } else {
FURI_LOG_E(TAG, "Decoded %s, raw=%s info=[%s,%s,%s,%s]", FURI_LOG_E(
info->name, info->raw, info->info1, info->info2, TAG,
info->info3, info->info4); "Decoded %s, raw=%s info=[%s,%s,%s,%s]",
info->name,
info->raw,
info->info1,
info->info2,
info->info3,
info->info4);
/* The message was correctly decoded: fill the info structure /* The message was correctly decoded: fill the info structure
* with the decoded signal. The decoder may not implement offset/len * with the decoded signal. The decoder may not implement offset/len
* filling of the structure. In such case we have no info and * filling of the structure. In such case we have no info and
* pulses_count will be set to zero. */ * pulses_count will be set to zero. */
if (info->pulses_count) { if(info->pulses_count) {
info->bits_bytes = (info->pulses_count+7)/8; // Round to full byte. info->bits_bytes = (info->pulses_count + 7) / 8; // Round to full byte.
info->bits = malloc(info->bits_bytes); info->bits = malloc(info->bits_bytes);
bitmap_copy(info->bits,info->bits_bytes,0, bitmap_copy(
bitmap,bitmap_size,info->start_off, info->bits,
info->pulses_count); info->bits_bytes,
0,
bitmap,
bitmap_size,
info->start_off,
info->pulses_count);
} }
} }
free(bitmap); free(bitmap);

View file

@ -13,57 +13,56 @@
* but it's logical representation stored in the app->msg_info bitmap, where * but it's logical representation stored in the app->msg_info bitmap, where
* each 1 or 0 means a puls or gap for the specified short pulse duration time * each 1 or 0 means a puls or gap for the specified short pulse duration time
* (te). */ * (te). */
bool save_signal(ProtoViewApp *app, const char *filename) { bool save_signal(ProtoViewApp* app, const char* filename) {
/* We have a message at all? */ /* We have a message at all? */
if (app->msg_info == NULL || app->msg_info->pulses_count == 0) return false; if(app->msg_info == NULL || app->msg_info->pulses_count == 0) return false;
Storage *storage = furi_record_open(RECORD_STORAGE); Storage* storage = furi_record_open(RECORD_STORAGE);
FlipperFormat *file = flipper_format_file_alloc(storage); FlipperFormat* file = flipper_format_file_alloc(storage);
Stream *stream = flipper_format_get_raw_stream(file); Stream* stream = flipper_format_get_raw_stream(file);
FuriString *file_content = NULL; FuriString* file_content = NULL;
bool success = true; bool success = true;
if (flipper_format_file_open_always(file, filename)) { if(flipper_format_file_open_always(file, filename)) {
/* Write the file header. */ /* Write the file header. */
FuriString *file_content = furi_string_alloc(); FuriString* file_content = furi_string_alloc();
const char *preset_id = ProtoViewModulations[app->modulation].id; const char* preset_id = ProtoViewModulations[app->modulation].id;
furi_string_printf(file_content, furi_string_printf(
"Filetype: Flipper SubGhz RAW File\n" file_content,
"Version: 1\n" "Filetype: Flipper SubGhz RAW File\n"
"Frequency: %ld\n" "Version: 1\n"
"Preset: %s\n", "Frequency: %ld\n"
app->frequency, "Preset: %s\n",
preset_id ? preset_id : "FuriHalSubGhzPresetCustom"); app->frequency,
preset_id ? preset_id : "FuriHalSubGhzPresetCustom");
/* For custom modulations, we need to emit a set of registers. */ /* For custom modulations, we need to emit a set of registers. */
if (preset_id == NULL) { if(preset_id == NULL) {
FuriString *custom = furi_string_alloc(); FuriString* custom = furi_string_alloc();
uint8_t *regs = ProtoViewModulations[app->modulation].custom; uint8_t* regs = ProtoViewModulations[app->modulation].custom;
furi_string_printf(custom, furi_string_printf(
custom,
"Custom_preset_module: CC1101\n" "Custom_preset_module: CC1101\n"
"Custom_preset_data: "); "Custom_preset_data: ");
for (int j = 0; regs[j]; j += 2) { for(int j = 0; regs[j]; j += 2) {
furi_string_cat_printf(custom, "%02X %02X ", furi_string_cat_printf(custom, "%02X %02X ", (int)regs[j], (int)regs[j + 1]);
(int)regs[j], (int)regs[j+1]);
} }
size_t len = furi_string_size(file_content); size_t len = furi_string_size(file_content);
furi_string_set_char(custom,len-1,'\n'); furi_string_set_char(custom, len - 1, '\n');
furi_string_cat(file_content,custom); furi_string_cat(file_content, custom);
furi_string_free(custom); furi_string_free(custom);
} }
/* We always save raw files. */ /* We always save raw files. */
furi_string_cat_printf(file_content, furi_string_cat_printf(
"Protocol: RAW\n" file_content,
"RAW_Data: -10000\n"); // Start with 10 ms of gap "Protocol: RAW\n"
"RAW_Data: -10000\n"); // Start with 10 ms of gap
/* Write header. */ /* Write header. */
size_t len = furi_string_size(file_content); size_t len = furi_string_size(file_content);
if (stream_write(stream, if(stream_write(stream, (uint8_t*)furi_string_get_cstr(file_content), len) != len) {
(uint8_t*) furi_string_get_cstr(file_content), len)
!= len)
{
FURI_LOG_W(TAG, "Short write to file"); FURI_LOG_W(TAG, "Short write to file");
success = false; success = false;
goto write_err; goto write_err;
@ -76,15 +75,13 @@ bool save_signal(ProtoViewApp *app, const char *filename) {
uint32_t this_line_samples = 0; uint32_t this_line_samples = 0;
uint32_t max_line_samples = 100; uint32_t max_line_samples = 100;
uint32_t idx = 0; // Iindex in the signal bitmap. uint32_t idx = 0; // Iindex in the signal bitmap.
ProtoViewMsgInfo *i = app->msg_info; ProtoViewMsgInfo* i = app->msg_info;
while(idx < i->pulses_count) { while(idx < i->pulses_count) {
bool level = bitmap_get(i->bits,i->bits_bytes,idx); bool level = bitmap_get(i->bits, i->bits_bytes, idx);
uint32_t te_times = 1; uint32_t te_times = 1;
idx++; idx++;
/* Count the duration of the current pulse/gap. */ /* Count the duration of the current pulse/gap. */
while(idx < i->pulses_count && while(idx < i->pulses_count && bitmap_get(i->bits, i->bits_bytes, idx) == level) {
bitmap_get(i->bits,i->bits_bytes,idx) == level)
{
te_times++; te_times++;
idx++; idx++;
} }
@ -92,32 +89,29 @@ bool save_signal(ProtoViewApp *app, const char *filename) {
// next gap or pulse. // next gap or pulse.
int32_t dur = (int32_t)i->short_pulse_dur * te_times; int32_t dur = (int32_t)i->short_pulse_dur * te_times;
if (level == 0) dur = -dur; /* Negative is gap in raw files. */ if(level == 0) dur = -dur; /* Negative is gap in raw files. */
/* Emit the sample. If this is the first sample of the line, /* Emit the sample. If this is the first sample of the line,
* also emit the RAW_Data: field. */ * also emit the RAW_Data: field. */
if (this_line_samples == 0) if(this_line_samples == 0) furi_string_cat_printf(file_content, "RAW_Data: ");
furi_string_cat_printf(file_content,"RAW_Data: "); furi_string_cat_printf(file_content, "%d ", (int)dur);
furi_string_cat_printf(file_content,"%d ",(int)dur);
this_line_samples++; this_line_samples++;
/* Store the current set of samples on disk, when we reach a /* Store the current set of samples on disk, when we reach a
* given number or the end of the signal. */ * given number or the end of the signal. */
bool end_reached = (idx == i->pulses_count); bool end_reached = (idx == i->pulses_count);
if (this_line_samples == max_line_samples || end_reached) { if(this_line_samples == max_line_samples || end_reached) {
/* If that's the end, terminate the signal with a long /* If that's the end, terminate the signal with a long
* gap. */ * gap. */
if (end_reached) furi_string_cat_printf(file_content,"-10000 "); if(end_reached) furi_string_cat_printf(file_content, "-10000 ");
/* We always have a trailing space in the last sample. Make it /* We always have a trailing space in the last sample. Make it
* a newline. */ * a newline. */
size_t len = furi_string_size(file_content); size_t len = furi_string_size(file_content);
furi_string_set_char(file_content,len-1,'\n'); furi_string_set_char(file_content, len - 1, '\n');
if (stream_write(stream, if(stream_write(stream, (uint8_t*)furi_string_get_cstr(file_content), len) !=
(uint8_t*) furi_string_get_cstr(file_content), len) {
len) != len)
{
FURI_LOG_W(TAG, "Short write to file"); FURI_LOG_W(TAG, "Short write to file");
success = false; success = false;
goto write_err; goto write_err;
@ -136,6 +130,6 @@ bool save_signal(ProtoViewApp *app, const char *filename) {
write_err: write_err:
furi_record_close(RECORD_STORAGE); furi_record_close(RECORD_STORAGE);
flipper_format_free(file); flipper_format_free(file);
if (file_content != NULL) furi_string_free(file_content); if(file_content != NULL) furi_string_free(file_content);
return success; return success;
} }

View file

@ -10,36 +10,31 @@
/* Return the ID of the currently selected subview, of the current /* Return the ID of the currently selected subview, of the current
* view. */ * view. */
int get_current_subview(ProtoViewApp *app) { int get_current_subview(ProtoViewApp* app) {
return app->current_subview[app->current_view]; return app->current_subview[app->current_view];
} }
/* Called by view rendering callback that has subviews, to show small triangles /* Called by view rendering callback that has subviews, to show small triangles
* facing down/up if there are other subviews the user can access with up * facing down/up if there are other subviews the user can access with up
* and down. */ * and down. */
void show_available_subviews(Canvas *canvas, ProtoViewApp *app, void show_available_subviews(Canvas* canvas, ProtoViewApp* app, int last_subview) {
int last_subview)
{
int subview = get_current_subview(app); int subview = get_current_subview(app);
if (subview != 0) if(subview != 0) canvas_draw_triangle(canvas, 120, 5, 8, 5, CanvasDirectionBottomToTop);
canvas_draw_triangle(canvas,120,5,8,5,CanvasDirectionBottomToTop); if(subview != last_subview - 1)
if (subview != last_subview-1) canvas_draw_triangle(canvas, 120, 59, 8, 5, CanvasDirectionTopToBottom);
canvas_draw_triangle(canvas,120,59,8,5,CanvasDirectionTopToBottom);
} }
/* Handle up/down keys when we are in a subview. If the function catched /* Handle up/down keys when we are in a subview. If the function catched
* such keypress, it returns true, so that the actual view input callback * such keypress, it returns true, so that the actual view input callback
* knows it can just return ASAP without doing anything. */ * knows it can just return ASAP without doing anything. */
bool process_subview_updown(ProtoViewApp *app, InputEvent input, int last_subview) { bool process_subview_updown(ProtoViewApp* app, InputEvent input, int last_subview) {
int subview = get_current_subview(app); int subview = get_current_subview(app);
if (input.type == InputTypePress) { if(input.type == InputTypePress) {
if (input.key == InputKeyUp) { if(input.key == InputKeyUp) {
if (subview != 0) if(subview != 0) app->current_subview[app->current_view]--;
app->current_subview[app->current_view]--;
return true; return true;
} else if (input.key == InputKeyDown) { } else if(input.key == InputKeyDown) {
if (subview != last_subview-1) if(subview != last_subview - 1) app->current_subview[app->current_view]++;
app->current_subview[app->current_view]++;
return true; return true;
} }
} }
@ -62,43 +57,37 @@ bool process_subview_updown(ProtoViewApp *app, InputEvent input, int last_subvie
* *
* Note: if the buffer is not a null-termined zero string, what it contains will * Note: if the buffer is not a null-termined zero string, what it contains will
* be used as initial input for the user. */ * be used as initial input for the user. */
void show_keyboard(ProtoViewApp *app, char *buffer, uint32_t buflen, void show_keyboard(ProtoViewApp* app, char* buffer, uint32_t buflen, void (*done_callback)(void*)) {
void (*done_callback)(void*))
{
app->show_text_input = true; app->show_text_input = true;
app->text_input_buffer = buffer; app->text_input_buffer = buffer;
app->text_input_buffer_len = buflen; app->text_input_buffer_len = buflen;
app->text_input_done_callback = done_callback; app->text_input_done_callback = done_callback;
} }
void dismiss_keyboard(ProtoViewApp *app) { void dismiss_keyboard(ProtoViewApp* app) {
view_dispatcher_stop(app->view_dispatcher); view_dispatcher_stop(app->view_dispatcher);
} }
/* =========================== Canvas extensions ============================ */ /* =========================== Canvas extensions ============================ */
void canvas_draw_str_with_border(Canvas* canvas, uint8_t x, uint8_t y, const char* str, Color text_color, Color border_color) void canvas_draw_str_with_border(
{ Canvas* canvas,
uint8_t x,
uint8_t y,
const char* str,
Color text_color,
Color border_color) {
struct { struct {
uint8_t x; uint8_t y; uint8_t x;
} dir[8] = { uint8_t y;
{-1,-1}, } dir[8] = {{-1, -1}, {0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}};
{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 /* Rotate in all the directions writing the same string to create a
* border, then write the actual string in the other color in the * border, then write the actual string in the other color in the
* middle. */ * middle. */
canvas_set_color(canvas, border_color); canvas_set_color(canvas, border_color);
for (int j = 0; j < 8; j++) for(int j = 0; j < 8; j++) canvas_draw_str(canvas, x + dir[j].x, y + dir[j].y, str);
canvas_draw_str(canvas,x+dir[j].x,y+dir[j].y,str);
canvas_set_color(canvas, text_color); canvas_set_color(canvas, text_color);
canvas_draw_str(canvas,x,y,str); canvas_draw_str(canvas, x, y, str);
canvas_set_color(canvas, ColorBlack); canvas_set_color(canvas, ColorBlack);
} }

View file

@ -7,47 +7,46 @@
/* Read directly from the G0 CC1101 pin, and draw a black or white /* Read directly from the G0 CC1101 pin, and draw a black or white
* dot depending on the level. */ * dot depending on the level. */
void render_view_direct_sampling(Canvas *const canvas, ProtoViewApp *app) { void render_view_direct_sampling(Canvas* const canvas, ProtoViewApp* app) {
if (!app->direct_sampling_enabled) { if(!app->direct_sampling_enabled) {
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas,2,9,"Direct sampling is a special"); canvas_draw_str(canvas, 2, 9, "Direct sampling is a special");
canvas_draw_str(canvas,2,18,"mode that displays the signal"); canvas_draw_str(canvas, 2, 18, "mode that displays the signal");
canvas_draw_str(canvas,2,27,"captured in real time. Like in"); canvas_draw_str(canvas, 2, 27, "captured in real time. Like in");
canvas_draw_str(canvas,2,36,"a old CRT TV. It's very slow."); canvas_draw_str(canvas, 2, 36, "a old CRT TV. It's very slow.");
canvas_draw_str(canvas,2,45,"Can crash your Flipper."); canvas_draw_str(canvas, 2, 45, "Can crash your Flipper.");
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas,14,60,"To enable press OK"); canvas_draw_str(canvas, 14, 60, "To enable press OK");
return; return;
} }
for (int y = 0; y < 64; y++) { for(int y = 0; y < 64; y++) {
for (int x = 0; x < 128; x++) { for(int x = 0; x < 128; x++) {
bool level = furi_hal_gpio_read(&gpio_cc1101_g0); bool level = furi_hal_gpio_read(&gpio_cc1101_g0);
if (level) canvas_draw_dot(canvas,x,y); if(level) canvas_draw_dot(canvas, x, y);
/* Busy loop: this is a terrible approach as it blocks /* Busy loop: this is a terrible approach as it blocks
* everything else, but for now it's the best we can do * everything else, but for now it's the best we can do
* to obtain direct data with some spacing. */ * to obtain direct data with some spacing. */
uint32_t x = 250; while(x--); uint32_t x = 250;
while(x--)
;
} }
} }
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
canvas_draw_str_with_border(canvas,36,60,"Direct sampling", canvas_draw_str_with_border(canvas, 36, 60, "Direct sampling", ColorWhite, ColorBlack);
ColorWhite,ColorBlack);
} }
/* Handle input */ /* Handle input */
void process_input_direct_sampling(ProtoViewApp *app, InputEvent input) { void process_input_direct_sampling(ProtoViewApp* app, InputEvent input) {
if (input.type == InputTypePress && input.key == InputKeyOk) { if(input.type == InputTypePress && input.key == InputKeyOk) {
app->direct_sampling_enabled = !app->direct_sampling_enabled; app->direct_sampling_enabled = !app->direct_sampling_enabled;
} }
} }
/* Enter view. Stop the subghz thread to prevent access as we read /* Enter view. Stop the subghz thread to prevent access as we read
* the CC1101 data directly. */ * the CC1101 data directly. */
void view_enter_direct_sampling(ProtoViewApp *app) { void view_enter_direct_sampling(ProtoViewApp* app) {
if (app->txrx->txrx_state == TxRxStateRx && if(app->txrx->txrx_state == TxRxStateRx && !app->txrx->debug_timer_sampling) {
!app->txrx->debug_timer_sampling)
{
subghz_worker_stop(app->txrx->worker); subghz_worker_stop(app->txrx->worker);
} else { } else {
raw_sampling_worker_stop(app); raw_sampling_worker_stop(app);
@ -55,10 +54,8 @@ void view_enter_direct_sampling(ProtoViewApp *app) {
} }
/* Exit view. Restore the subghz thread. */ /* Exit view. Restore the subghz thread. */
void view_exit_direct_sampling(ProtoViewApp *app) { void view_exit_direct_sampling(ProtoViewApp* app) {
if (app->txrx->txrx_state == TxRxStateRx && if(app->txrx->txrx_state == TxRxStateRx && !app->txrx->debug_timer_sampling) {
!app->txrx->debug_timer_sampling)
{
subghz_worker_start(app->txrx->worker); subghz_worker_start(app->txrx->worker);
} else { } else {
raw_sampling_worker_start(app); raw_sampling_worker_start(app);

View file

@ -18,11 +18,11 @@ typedef struct {
* so that the user can see what they are saving. With left/right * so that the user can see what they are saving. With left/right
* you can move to next rows. Here we store where we are. */ * you can move to next rows. Here we store where we are. */
uint32_t signal_display_start_row; uint32_t signal_display_start_row;
char *filename; char* filename;
} InfoViewPrivData; } InfoViewPrivData;
/* Render the view with the detected message information. */ /* Render the view with the detected message information. */
static void render_subview_main(Canvas *const canvas, ProtoViewApp *app) { static void render_subview_main(Canvas* const canvas, ProtoViewApp* app) {
/* Protocol name as title. */ /* Protocol name as title. */
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
uint8_t y = 8, lineheight = 10; uint8_t y = 8, lineheight = 10;
@ -32,27 +32,35 @@ static void render_subview_main(Canvas *const canvas, ProtoViewApp *app) {
/* Info fields. */ /* Info fields. */
char buf[128]; char buf[128];
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
if (app->msg_info->raw[0]) { if(app->msg_info->raw[0]) {
snprintf(buf,sizeof(buf),"Raw: %s", app->msg_info->raw); snprintf(buf, sizeof(buf), "Raw: %s", app->msg_info->raw);
canvas_draw_str(canvas, 0, y, buf); canvas_draw_str(canvas, 0, y, buf);
y += lineheight; y += lineheight;
} }
canvas_draw_str(canvas, 0, y, app->msg_info->info1); y += lineheight; canvas_draw_str(canvas, 0, y, app->msg_info->info1);
canvas_draw_str(canvas, 0, y, app->msg_info->info2); y += lineheight; y += lineheight;
canvas_draw_str(canvas, 0, y, app->msg_info->info3); y += lineheight; canvas_draw_str(canvas, 0, y, app->msg_info->info2);
canvas_draw_str(canvas, 0, y, app->msg_info->info4); y += lineheight; y += lineheight;
canvas_draw_str(canvas, 0, y, app->msg_info->info3);
y += lineheight;
canvas_draw_str(canvas, 0, y, app->msg_info->info4);
y += lineheight;
y = 37; y = 37;
lineheight = 7; lineheight = 7;
canvas_draw_str(canvas, 119, y, "s"); y += lineheight; canvas_draw_str(canvas, 119, y, "s");
canvas_draw_str(canvas, 119, y, "a"); y += lineheight; y += lineheight;
canvas_draw_str(canvas, 119, y, "v"); y += lineheight; canvas_draw_str(canvas, 119, y, "a");
canvas_draw_str(canvas, 119, y, "e"); y += lineheight; y += lineheight;
canvas_draw_str(canvas, 119, y, "v");
y += lineheight;
canvas_draw_str(canvas, 119, y, "e");
y += lineheight;
} }
/* Render view with save option. */ /* Render view with save option. */
static void render_subview_save(Canvas *const canvas, ProtoViewApp *app) { static void render_subview_save(Canvas* const canvas, ProtoViewApp* app) {
InfoViewPrivData *privdata = app->view_privdata; InfoViewPrivData* privdata = app->view_privdata;
/* Display our signal in digital form: here we don't show the /* Display our signal in digital form: here we don't show the
* signal with the exact timing of the received samples, but as it * signal with the exact timing of the received samples, but as it
@ -61,21 +69,20 @@ static void render_subview_save(Canvas *const canvas, ProtoViewApp *app) {
uint8_t rowheight = 11; uint8_t rowheight = 11;
uint8_t bitwidth = 4; uint8_t bitwidth = 4;
uint8_t bitheight = 5; uint8_t bitheight = 5;
uint32_t idx = privdata->signal_display_start_row * (128/4); uint32_t idx = privdata->signal_display_start_row * (128 / 4);
bool prevbit = false; bool prevbit = false;
for (uint8_t y = bitheight+12; y <= rows*rowheight; y += rowheight) { for(uint8_t y = bitheight + 12; y <= rows * rowheight; y += rowheight) {
for (uint8_t x = 0; x < 128; x += 4) { for(uint8_t x = 0; x < 128; x += 4) {
bool bit = bitmap_get(app->msg_info->bits, bool bit = bitmap_get(app->msg_info->bits, app->msg_info->bits_bytes, idx);
app->msg_info->bits_bytes,idx); uint8_t prevy = y + prevbit * (bitheight * -1) - 1;
uint8_t prevy = y + prevbit*(bitheight*-1) - 1; uint8_t thisy = y + bit * (bitheight * -1) - 1;
uint8_t thisy = y + bit*(bitheight*-1) - 1; canvas_draw_line(canvas, x, prevy, x, thisy);
canvas_draw_line(canvas,x,prevy,x,thisy); canvas_draw_line(canvas, x, thisy, x + bitwidth - 1, thisy);
canvas_draw_line(canvas,x,thisy,x+bitwidth-1,thisy);
prevbit = bit; prevbit = bit;
if (idx >= app->msg_info->pulses_count) { if(idx >= app->msg_info->pulses_count) {
canvas_set_color(canvas, ColorWhite); canvas_set_color(canvas, ColorWhite);
canvas_draw_dot(canvas, x+1,thisy); canvas_draw_dot(canvas, x + 1, thisy);
canvas_draw_dot(canvas, x+3,thisy); canvas_draw_dot(canvas, x + 3, thisy);
canvas_set_color(canvas, ColorBlack); canvas_set_color(canvas, ColorBlack);
} }
idx++; // Draw next bit idx++; // Draw next bit
@ -87,28 +94,32 @@ static void render_subview_save(Canvas *const canvas, ProtoViewApp *app) {
} }
/* Render the selected subview of this view. */ /* Render the selected subview of this view. */
void render_view_info(Canvas *const canvas, ProtoViewApp *app) { void render_view_info(Canvas* const canvas, ProtoViewApp* app) {
if (app->signal_decoded == false) { if(app->signal_decoded == false) {
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 30,36,"No signal decoded"); canvas_draw_str(canvas, 30, 36, "No signal decoded");
return; return;
} }
show_available_subviews(canvas,app,SubViewInfoLast); show_available_subviews(canvas, app, SubViewInfoLast);
switch(app->current_subview[app->current_view]) { switch(app->current_subview[app->current_view]) {
case SubViewInfoMain: render_subview_main(canvas,app); break; case SubViewInfoMain:
case SubViewInfoSave: render_subview_save(canvas,app); break; render_subview_main(canvas, app);
break;
case SubViewInfoSave:
render_subview_save(canvas, app);
break;
} }
} }
/* The user typed the file name. Let's save it and remove the keyboard /* The user typed the file name. Let's save it and remove the keyboard
* view. */ * view. */
void text_input_done_callback(void* context) { void text_input_done_callback(void* context) {
ProtoViewApp *app = context; ProtoViewApp* app = context;
InfoViewPrivData *privdata = app->view_privdata; InfoViewPrivData* privdata = app->view_privdata;
FuriString *save_path = furi_string_alloc_printf( FuriString* save_path =
"%s/%s.sub", EXT_PATH("subghz"), privdata->filename); furi_string_alloc_printf("%s/%s.sub", EXT_PATH("subghz"), privdata->filename);
save_signal(app, furi_string_get_cstr(save_path)); save_signal(app, furi_string_get_cstr(save_path));
furi_string_free(save_path); furi_string_free(save_path);
@ -118,48 +129,46 @@ void text_input_done_callback(void* context) {
/* Replace all the occurrences of character c1 with c2 in the specified /* Replace all the occurrences of character c1 with c2 in the specified
* string. */ * string. */
void str_replace(char *buf, char c1, char c2) { void str_replace(char* buf, char c1, char c2) {
char *p = buf; char* p = buf;
while(*p) { while(*p) {
if (*p == c1) *p = c2; if(*p == c1) *p = c2;
p++; p++;
} }
} }
/* Set a random filename the user can edit. */ /* Set a random filename the user can edit. */
void set_signal_random_filename(ProtoViewApp *app, char *buf, size_t buflen) { void set_signal_random_filename(ProtoViewApp* app, char* buf, size_t buflen) {
char suffix[6]; char suffix[6];
set_random_name(suffix,sizeof(suffix)); set_random_name(suffix, sizeof(suffix));
snprintf(buf,buflen,"%.10s-%s-%d",app->msg_info->name,suffix,rand()%1000); snprintf(buf, buflen, "%.10s-%s-%d", app->msg_info->name, suffix, rand() % 1000);
str_replace(buf,' ','_'); str_replace(buf, ' ', '_');
str_replace(buf,'-','_'); str_replace(buf, '-', '_');
str_replace(buf,'/','_'); str_replace(buf, '/', '_');
} }
/* Handle input for the info view. */ /* Handle input for the info view. */
void process_input_info(ProtoViewApp *app, InputEvent input) { void process_input_info(ProtoViewApp* app, InputEvent input) {
if (process_subview_updown(app,input,SubViewInfoLast)) return; if(process_subview_updown(app, input, SubViewInfoLast)) return;
InfoViewPrivData *privdata = app->view_privdata; InfoViewPrivData* privdata = app->view_privdata;
int subview = get_current_subview(app); int subview = get_current_subview(app);
/* Main subview. */ /* Main subview. */
if (subview == SubViewInfoMain) { if(subview == SubViewInfoMain) {
if (input.type == InputTypeShort && input.key == InputKeyOk) { if(input.type == InputTypeShort && input.key == InputKeyOk) {
/* Reset the current sample to capture the next. */ /* Reset the current sample to capture the next. */
reset_current_signal(app); reset_current_signal(app);
} }
} else if (subview == SubViewInfoSave) { } else if(subview == SubViewInfoSave) {
/* Save subview. */ /* Save subview. */
if (input.type == InputTypePress && input.key == InputKeyRight) { if(input.type == InputTypePress && input.key == InputKeyRight) {
privdata->signal_display_start_row++; privdata->signal_display_start_row++;
} else if (input.type == InputTypePress && input.key == InputKeyLeft) { } else if(input.type == InputTypePress && input.key == InputKeyLeft) {
if (privdata->signal_display_start_row != 0) if(privdata->signal_display_start_row != 0) privdata->signal_display_start_row--;
privdata->signal_display_start_row--; } else if(input.type == InputTypePress && input.key == InputKeyOk) {
} else if (input.type == InputTypePress && input.key == InputKeyOk) {
privdata->filename = malloc(SAVE_FILENAME_LEN); privdata->filename = malloc(SAVE_FILENAME_LEN);
set_signal_random_filename(app,privdata->filename,SAVE_FILENAME_LEN); set_signal_random_filename(app, privdata->filename, SAVE_FILENAME_LEN);
show_keyboard(app, privdata->filename, SAVE_FILENAME_LEN, show_keyboard(app, privdata->filename, SAVE_FILENAME_LEN, text_input_done_callback);
text_input_done_callback);
} }
} }
} }

View file

@ -12,7 +12,7 @@
* *
* The 'idx' argument is the first sample to render in the circular * The 'idx' argument is the first sample to render in the circular
* buffer. */ * buffer. */
void render_signal(ProtoViewApp *app, Canvas *const canvas, RawSamplesBuffer *buf, uint32_t idx) { void render_signal(ProtoViewApp* app, Canvas* const canvas, RawSamplesBuffer* buf, uint32_t idx) {
canvas_set_color(canvas, ColorBlack); canvas_set_color(canvas, ColorBlack);
int rows = 8; int rows = 8;
@ -20,31 +20,29 @@ void render_signal(ProtoViewApp *app, Canvas *const canvas, RawSamplesBuffer *bu
uint32_t start_idx = idx; uint32_t start_idx = idx;
bool level = 0; bool level = 0;
uint32_t dur = 0, sample_num = 0; uint32_t dur = 0, sample_num = 0;
for (int row = 0; row < rows ; row++) { for(int row = 0; row < rows; row++) {
for (int x = 0; x < 128; x++) { for(int x = 0; x < 128; x++) {
int y = 3 + row*8; int y = 3 + row * 8;
if (dur < time_per_pixel/2) { if(dur < time_per_pixel / 2) {
/* Get more data. */ /* Get more data. */
raw_samples_get(buf, idx++, &level, &dur); raw_samples_get(buf, idx++, &level, &dur);
sample_num++; sample_num++;
} }
canvas_draw_line(canvas, x,y,x,y-(level*3)); canvas_draw_line(canvas, x, y, x, y - (level * 3));
/* Write a small triangle under the last sample detected. */ /* Write a small triangle under the last sample detected. */
if (app->signal_bestlen != 0 && if(app->signal_bestlen != 0 && sample_num + start_idx == app->signal_bestlen + 1) {
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+2); canvas_draw_dot(canvas, x, y + 3);
canvas_draw_dot(canvas,x-1,y+3); 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. */ sample_num++; /* Make sure we don't mark the next, too. */
} }
/* Remove from the current level duration the time we /* Remove from the current level duration the time we
* just plot. */ * just plot. */
if (dur > time_per_pixel) if(dur > time_per_pixel)
dur -= time_per_pixel; dur -= time_per_pixel;
else else
dur = 0; dur = 0;
@ -53,45 +51,46 @@ void render_signal(ProtoViewApp *app, Canvas *const canvas, RawSamplesBuffer *bu
} }
/* Raw pulses rendering. This is our default view. */ /* Raw pulses rendering. This is our default view. */
void render_view_raw_pulses(Canvas *const canvas, ProtoViewApp *app) { void render_view_raw_pulses(Canvas* const canvas, ProtoViewApp* app) {
/* Show signal. */ /* Show signal. */
render_signal(app, canvas, DetectedSamples, app->signal_offset); render_signal(app, canvas, DetectedSamples, app->signal_offset);
/* Show signal information. */ /* Show signal information. */
char buf[64]; char buf[64];
snprintf(buf,sizeof(buf),"%luus", snprintf(buf, sizeof(buf), "%luus", (unsigned long)DetectedSamples->short_pulse_dur);
(unsigned long)DetectedSamples->short_pulse_dur);
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
canvas_draw_str_with_border(canvas, 97, 63, buf, ColorWhite, ColorBlack); canvas_draw_str_with_border(canvas, 97, 63, buf, ColorWhite, ColorBlack);
if (app->signal_decoded) { if(app->signal_decoded) {
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
canvas_draw_str_with_border(canvas, 1, 61, app->msg_info->name, ColorWhite, ColorBlack); canvas_draw_str_with_border(canvas, 1, 61, app->msg_info->name, ColorWhite, ColorBlack);
} }
} }
/* Handle input for the raw pulses view. */ /* Handle input for the raw pulses view. */
void process_input_raw_pulses(ProtoViewApp *app, InputEvent input) { void process_input_raw_pulses(ProtoViewApp* app, InputEvent input) {
if (input.type == InputTypeRepeat) { if(input.type == InputTypeRepeat) {
/* Handle panning of the signal window. Long pressing /* Handle panning of the signal window. Long pressing
* right will show successive samples, long pressing left * right will show successive samples, long pressing left
* previous samples. */ * previous samples. */
if (input.key == InputKeyRight) app->signal_offset++; if(input.key == InputKeyRight)
else if (input.key == InputKeyLeft) app->signal_offset--; app->signal_offset++;
else if (input.key == InputKeyOk) { else if(input.key == InputKeyLeft)
app->signal_offset--;
else if(input.key == InputKeyOk) {
app->signal_offset = 0; app->signal_offset = 0;
app->us_scale = PROTOVIEW_RAW_VIEW_DEFAULT_SCALE; app->us_scale = PROTOVIEW_RAW_VIEW_DEFAULT_SCALE;
} }
} else if (input.type == InputTypeShort) { } else if(input.type == InputTypeShort) {
if (input.key == InputKeyOk) { if(input.key == InputKeyOk) {
/* Reset the current sample to capture the next. */ /* Reset the current sample to capture the next. */
reset_current_signal(app); reset_current_signal(app);
} else if (input.key == InputKeyDown) { } else if(input.key == InputKeyDown) {
/* Rescaling. The set becomes finer under 50us per pixel. */ /* Rescaling. The set becomes finer under 50us per pixel. */
uint32_t scale_step = app->us_scale >= 50 ? 50 : 10; uint32_t scale_step = app->us_scale >= 50 ? 50 : 10;
if (app->us_scale < 500) app->us_scale += scale_step; if(app->us_scale < 500) app->us_scale += scale_step;
} else if (input.key == InputKeyUp) { } else if(input.key == InputKeyUp) {
uint32_t scale_step = app->us_scale > 50 ? 50 : 10; uint32_t scale_step = app->us_scale > 50 ? 50 : 10;
if (app->us_scale > 10) app->us_scale -= scale_step; if(app->us_scale > 10) app->us_scale -= scale_step;
} }
} }
} }

View file

@ -6,30 +6,30 @@
/* Renders a single view with frequency and modulation setting. However /* Renders a single view with frequency and modulation setting. However
* this are logically two different views, and only one of the settings * this are logically two different views, and only one of the settings
* will be highlighted. */ * will be highlighted. */
void render_view_settings(Canvas *const canvas, ProtoViewApp *app) { void render_view_settings(Canvas* const canvas, ProtoViewApp* app) {
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
if (app->current_view == ViewFrequencySettings) if(app->current_view == ViewFrequencySettings)
canvas_draw_str_with_border(canvas,1,10,"Frequency",ColorWhite,ColorBlack); canvas_draw_str_with_border(canvas, 1, 10, "Frequency", ColorWhite, ColorBlack);
else else
canvas_draw_str(canvas,1,10,"Frequency"); canvas_draw_str(canvas, 1, 10, "Frequency");
if (app->current_view == ViewModulationSettings) if(app->current_view == ViewModulationSettings)
canvas_draw_str_with_border(canvas,70,10,"Modulation",ColorWhite,ColorBlack); canvas_draw_str_with_border(canvas, 70, 10, "Modulation", ColorWhite, ColorBlack);
else else
canvas_draw_str(canvas,70,10,"Modulation"); canvas_draw_str(canvas, 70, 10, "Modulation");
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas,10,61,"Use up and down to modify"); canvas_draw_str(canvas, 10, 61, "Use up and down to modify");
if (app->txrx->debug_timer_sampling) if(app->txrx->debug_timer_sampling)
canvas_draw_str(canvas,3,52,"(DEBUG timer sampling is ON)"); canvas_draw_str(canvas, 3, 52, "(DEBUG timer sampling is ON)");
/* Show frequency. We can use big numbers font since it's just a number. */ /* Show frequency. We can use big numbers font since it's just a number. */
if (app->current_view == ViewFrequencySettings) { if(app->current_view == ViewFrequencySettings) {
char buf[16]; char buf[16];
snprintf(buf,sizeof(buf),"%.2f",(double)app->frequency/1000000); snprintf(buf, sizeof(buf), "%.2f", (double)app->frequency / 1000000);
canvas_set_font(canvas, FontBigNumbers); canvas_set_font(canvas, FontBigNumbers);
canvas_draw_str(canvas, 30, 40, buf); canvas_draw_str(canvas, 30, 40, buf);
} else if (app->current_view == ViewModulationSettings) { } else if(app->current_view == ViewModulationSettings) {
int current = app->modulation; int current = app->modulation;
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 33, 39, ProtoViewModulations[current].name); canvas_draw_str(canvas, 33, 39, ProtoViewModulations[current].name);
@ -37,13 +37,13 @@ void render_view_settings(Canvas *const canvas, ProtoViewApp *app) {
} }
/* Handle input for the settings view. */ /* Handle input for the settings view. */
void process_input_settings(ProtoViewApp *app, InputEvent input) { void process_input_settings(ProtoViewApp* app, InputEvent input) {
if (input.type == InputTypeLong && input.key == InputKeyOk) { if(input.type == InputTypeLong && input.key == InputKeyOk) {
/* Long pressing to OK sets the default frequency and /* Long pressing to OK sets the default frequency and
* modulation. */ * modulation. */
app->frequency = subghz_setting_get_default_frequency(app->setting); app->frequency = subghz_setting_get_default_frequency(app->setting);
app->modulation = 0; app->modulation = 0;
} else if (0 && input.type == InputTypeLong && input.key == InputKeyDown) { } else if(0 && input.type == InputTypeLong && input.key == InputKeyDown) {
/* Long pressing to down switches between normal and debug /* Long pressing to down switches between normal and debug
* timer sampling mode. NOTE: this feature is disabled for users, * timer sampling mode. NOTE: this feature is disabled for users,
* only useful for devs (if useful at all). */ * only useful for devs (if useful at all). */
@ -55,42 +55,40 @@ void process_input_settings(ProtoViewApp *app, InputEvent input) {
app->txrx->debug_timer_sampling = !app->txrx->debug_timer_sampling; app->txrx->debug_timer_sampling = !app->txrx->debug_timer_sampling;
radio_begin(app); radio_begin(app);
radio_rx(app); radio_rx(app);
} else if (input.type == InputTypePress && } else if(input.type == InputTypePress && (input.key != InputKeyDown || input.key != InputKeyUp)) {
(input.key != InputKeyDown || input.key != InputKeyUp))
{
/* Handle up and down to change frequency or modulation. */ /* Handle up and down to change frequency or modulation. */
if (app->current_view == ViewFrequencySettings) { if(app->current_view == ViewFrequencySettings) {
size_t curidx = 0, i; size_t curidx = 0, i;
size_t count = subghz_setting_get_frequency_count(app->setting); size_t count = subghz_setting_get_frequency_count(app->setting);
/* Scan the list of frequencies to check for the index of the /* Scan the list of frequencies to check for the index of the
* currently set frequency. */ * currently set frequency. */
for(i = 0; i < count; i++) { for(i = 0; i < count; i++) {
uint32_t freq = subghz_setting_get_frequency(app->setting,i); uint32_t freq = subghz_setting_get_frequency(app->setting, i);
if (freq == app->frequency) { if(freq == app->frequency) {
curidx = i; curidx = i;
break; break;
} }
} }
if (i == count) return; /* Should never happen. */ if(i == count) return; /* Should never happen. */
if (input.key == InputKeyUp) { if(input.key == InputKeyUp) {
curidx = curidx == 0 ? count-1 : curidx-1; curidx = curidx == 0 ? count - 1 : curidx - 1;
} else if (input.key == InputKeyDown) { } else if(input.key == InputKeyDown) {
curidx = (curidx+1) % count; curidx = (curidx + 1) % count;
} else { } else {
return; return;
} }
app->frequency = subghz_setting_get_frequency(app->setting,curidx); app->frequency = subghz_setting_get_frequency(app->setting, curidx);
} else if (app->current_view == ViewModulationSettings) { } else if(app->current_view == ViewModulationSettings) {
uint32_t count = 0; uint32_t count = 0;
uint32_t modid = app->modulation; uint32_t modid = app->modulation;
while(ProtoViewModulations[count].name != NULL) count++; while(ProtoViewModulations[count].name != NULL) count++;
if (input.key == InputKeyUp) { if(input.key == InputKeyUp) {
modid = modid == 0 ? count-1 : modid-1; modid = modid == 0 ? count - 1 : modid - 1;
} else if (input.key == InputKeyDown) { } else if(input.key == InputKeyDown) {
modid = (modid+1) % count; modid = (modid + 1) % count;
} else { } else {
return; return;
} }
@ -106,9 +104,13 @@ void process_input_settings(ProtoViewApp *app, InputEvent input) {
/* When the user switches to some other view, if they changed the parameters /* When the user switches to some other view, if they changed the parameters
* we need to restart the radio with the right frequency and modulation. */ * we need to restart the radio with the right frequency and modulation. */
void view_exit_settings(ProtoViewApp *app) { void view_exit_settings(ProtoViewApp* app) {
if (app->txrx->freq_mod_changed) { if(app->txrx->freq_mod_changed) {
FURI_LOG_E(TAG, "Setting view, setting frequency/modulation to %lu %s", app->frequency, ProtoViewModulations[app->modulation].name); FURI_LOG_E(
TAG,
"Setting view, setting frequency/modulation to %lu %s",
app->frequency,
ProtoViewModulations[app->modulation].name);
radio_rx_end(app); radio_rx_end(app);
radio_begin(app); radio_begin(app);
radio_rx(app); radio_rx(app);

View file

@ -228,14 +228,16 @@ int32_t text_viewer_app(void* p) {
if(input.key == InputKeyBack) { if(input.key == InputKeyBack) {
break; break;
} else if(input.key == InputKeyUp) { } else if(input.key == InputKeyUp) {
furi_check(furi_mutex_acquire(text_viewer->mutex, FuriWaitForever) == FuriStatusOk); furi_check(
furi_mutex_acquire(text_viewer->mutex, FuriWaitForever) == FuriStatusOk);
if(text_viewer->model->file_offset > 0) { if(text_viewer->model->file_offset > 0) {
text_viewer->model->file_offset -= TEXT_VIEWER_BYTES_PER_LINE; text_viewer->model->file_offset -= TEXT_VIEWER_BYTES_PER_LINE;
if(!text_viewer_read_file(text_viewer)) break; if(!text_viewer_read_file(text_viewer)) break;
} }
furi_mutex_release(text_viewer->mutex); furi_mutex_release(text_viewer->mutex);
} else if(input.key == InputKeyDown) { } else if(input.key == InputKeyDown) {
furi_check(furi_mutex_acquire(text_viewer->mutex, FuriWaitForever) == FuriStatusOk); furi_check(
furi_mutex_acquire(text_viewer->mutex, FuriWaitForever) == FuriStatusOk);
uint32_t last_byte_on_screen = uint32_t last_byte_on_screen =
text_viewer->model->file_offset + text_viewer->model->file_read_bytes; text_viewer->model->file_offset + text_viewer->model->file_read_bytes;
@ -245,7 +247,8 @@ int32_t text_viewer_app(void* p) {
} }
furi_mutex_release(text_viewer->mutex); furi_mutex_release(text_viewer->mutex);
} else if(input.key == InputKeyLeft) { } else if(input.key == InputKeyLeft) {
furi_check(furi_mutex_acquire(text_viewer->mutex, FuriWaitForever) == FuriStatusOk); furi_check(
furi_mutex_acquire(text_viewer->mutex, FuriWaitForever) == FuriStatusOk);
text_viewer->model->mode = !text_viewer->model->mode; text_viewer->model->mode = !text_viewer->model->mode;
furi_mutex_release(text_viewer->mutex); furi_mutex_release(text_viewer->mutex);
} else if(input.key == InputKeyRight) { } else if(input.key == InputKeyRight) {

View file

@ -66,9 +66,14 @@ const Interface ONE_WIRE = {
.allocator = unitemp_onewire_sensor_alloc, .allocator = unitemp_onewire_sensor_alloc,
.mem_releaser = unitemp_onewire_sensor_free, .mem_releaser = unitemp_onewire_sensor_free,
.updater = unitemp_onewire_sensor_update}; .updater = unitemp_onewire_sensor_update};
const Interface SPI = {
.name = "SPI",
.allocator = unitemp_spi_sensor_alloc,
.mem_releaser = unitemp_spi_sensor_free,
.updater = unitemp_spi_sensor_update};
//Перечень интерфейсов подключения //Перечень интерфейсов подключения
//static const Interface* interfaces[] = {&SINGLE_WIRE, &I2C, &ONE_WIRE}; //static const Interface* interfaces[] = {&SINGLE_WIRE, &I2C, &ONE_WIRE, &SPI};
//Перечень датчиков //Перечень датчиков
static const SensorType* sensorTypes[] = { static const SensorType* sensorTypes[] = {
&DHT11, &DHT11,
@ -87,7 +92,8 @@ static const SensorType* sensorTypes[] = {
&HDC1080, &HDC1080,
&BMP180, &BMP180,
&BMP280, &BMP280,
&BME280}; &BME280,
&MAX31855};
const SensorType* unitemp_sensors_getTypeFromInt(uint8_t index) { const SensorType* unitemp_sensors_getTypeFromInt(uint8_t index) {
if(index > SENSOR_TYPES_COUNT) return NULL; if(index > SENSOR_TYPES_COUNT) return NULL;
@ -166,7 +172,7 @@ uint8_t unitemp_gpio_getAviablePortsCount(const Interface* interface, const GPIO
} }
//Проверка для single wire //Проверка для single wire
if(interface == &SINGLE_WIRE) { if(interface == &SINGLE_WIRE || interface == &SPI) {
if(gpio_interfaces_list[i] == NULL || (unitemp_gpio_getFromIndex(i) == extraport)) { if(gpio_interfaces_list[i] == NULL || (unitemp_gpio_getFromIndex(i) == extraport)) {
aviable_ports_count++; aviable_ports_count++;
} }
@ -205,6 +211,13 @@ const GPIO*
return NULL; return NULL;
} }
} }
if(interface == &SPI) {
if(!((gpio_interfaces_list[0] == NULL || gpio_interfaces_list[0] == &SPI) &&
(gpio_interfaces_list[1] == NULL || gpio_interfaces_list[1] == &SPI) &&
(gpio_interfaces_list[3] == NULL || gpio_interfaces_list[3] == &SPI))) {
return NULL;
}
}
uint8_t aviable_index = 0; uint8_t aviable_index = 0;
for(uint8_t i = 0; i < GPIO_ITEMS; i++) { for(uint8_t i = 0; i < GPIO_ITEMS; i++) {
@ -222,7 +235,7 @@ const GPIO*
} }
} }
//Проверка для single wire //Проверка для single wire
if(interface == &SINGLE_WIRE) { if(interface == &SINGLE_WIRE || interface == &SPI) {
if(gpio_interfaces_list[i] == NULL || unitemp_gpio_getFromIndex(i) == extraport) { if(gpio_interfaces_list[i] == NULL || unitemp_gpio_getFromIndex(i) == extraport) {
if(aviable_index == index) { if(aviable_index == index) {
return unitemp_gpio_getFromIndex(i); return unitemp_gpio_getFromIndex(i);
@ -435,6 +448,11 @@ bool unitemp_sensors_save(void) {
stream_write_format( stream_write_format(
app->file_stream, "%d\n", unitemp_singlewire_sensorGetGPIO(sensor)->num); app->file_stream, "%d\n", unitemp_singlewire_sensorGetGPIO(sensor)->num);
} }
if(sensor->type->interface == &SPI) {
uint8_t gpio_num = ((SPISensor*)sensor->instance)->CS_pin->num;
stream_write_format(app->file_stream, "%d\n", gpio_num);
}
if(sensor->type->interface == &I2C) { if(sensor->type->interface == &I2C) {
stream_write_format( stream_write_format(
app->file_stream, "%X\n", ((I2CSensor*)sensor->instance)->currentI2CAdr); app->file_stream, "%X\n", ((I2CSensor*)sensor->instance)->currentI2CAdr);
@ -512,7 +530,7 @@ Sensor* unitemp_sensor_alloc(char* name, const SensorType* type, char* args) {
//Выход если датчик успешно развёрнут //Выход если датчик успешно развёрнут
if(status) { if(status) {
FURI_LOG_I(APP_NAME, "Sensor %s allocated", name); UNITEMP_DEBUG("Sensor %s allocated", name);
return sensor; return sensor;
} }
//Выход с очисткой если память для датчика не была выделена //Выход с очисткой если память для датчика не была выделена
@ -545,7 +563,6 @@ void unitemp_sensor_free(Sensor* sensor) {
FURI_LOG_E(APP_NAME, "Sensor %s memory is not released", sensor->name); FURI_LOG_E(APP_NAME, "Sensor %s memory is not released", sensor->name);
} }
free(sensor->name); free(sensor->name);
//free(sensor);
} }
void unitemp_sensors_free(void) { void unitemp_sensors_free(void) {
@ -573,7 +590,7 @@ bool unitemp_sensors_init(void) {
app->sensors[i]->name); app->sensors[i]->name);
result = false; result = false;
} }
UNITEMP_DEBUG("Sensor %s successfully initialized", app->sensors[i]->name); FURI_LOG_I(APP_NAME, "Sensor %s successfully initialized", app->sensors[i]->name);
} }
app->sensors_ready = true; app->sensors_ready = true;
return result; return result;

View file

@ -136,7 +136,7 @@ typedef struct Sensor {
extern const Interface SINGLE_WIRE; //Собственный однопроводной протокол датчиков DHTXX и AM23XX extern const Interface SINGLE_WIRE; //Собственный однопроводной протокол датчиков DHTXX и AM23XX
extern const Interface ONE_WIRE; //Однопроводной протокол Dallas extern const Interface ONE_WIRE; //Однопроводной протокол Dallas
extern const Interface I2C; //I2C_2 (PC0, PC1) extern const Interface I2C; //I2C_2 (PC0, PC1)
//extern const Interface SPI; extern const Interface SPI; //SPI_1 (MOSI - 2, MISO - 3, CS - 4, SCK - 5)
/* ============================= Датчик(и) ============================= */ /* ============================= Датчик(и) ============================= */
/** /**
@ -326,4 +326,5 @@ const GPIO*
#include "./sensors/BMP180.h" #include "./sensors/BMP180.h"
#include "./sensors/HTU21x.h" #include "./sensors/HTU21x.h"
#include "./sensors/HDC1080.h" #include "./sensors/HDC1080.h"
#include "./sensors/MAX31855.h"
#endif #endif

View file

@ -0,0 +1,89 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include <furi.h>
#include <furi_hal.h>
#include "SPISensor.h"
static uint8_t sensors_count = 0;
bool unitemp_spi_sensor_alloc(Sensor* sensor, char* args) {
if(args == NULL) return false;
//Создание инстанса датчика SPI
SPISensor* instance = malloc(sizeof(SPISensor));
if(instance == NULL) {
FURI_LOG_E(APP_NAME, "Sensor %s instance allocation error", sensor->name);
return false;
}
sensor->instance = instance;
//Определение GPIO chip select
int gpio = 255;
sscanf(args, "%d", &gpio);
instance->CS_pin = unitemp_gpio_getFromInt(gpio);
if(instance->CS_pin == NULL) {
FURI_LOG_E(APP_NAME, "Sensor %s GPIO setting error", sensor->name);
free(instance);
return false;
}
instance->spi = malloc(sizeof(FuriHalSpiBusHandle));
memcpy(instance->spi, &furi_hal_spi_bus_handle_external, sizeof(FuriHalSpiBusHandle));
instance->spi->cs = instance->CS_pin->pin;
bool status = sensor->type->allocator(sensor, args);
//Блокировка портов GPIO
sensors_count++;
unitemp_gpio_lock(unitemp_gpio_getFromInt(2), &SPI);
unitemp_gpio_lock(unitemp_gpio_getFromInt(3), &SPI);
unitemp_gpio_lock(unitemp_gpio_getFromInt(5), &SPI);
unitemp_gpio_lock(instance->CS_pin, &SPI);
return status;
}
bool unitemp_spi_sensor_free(Sensor* sensor) {
bool status = sensor->type->mem_releaser(sensor);
unitemp_gpio_unlock(((SPISensor*)sensor->instance)->CS_pin);
free(((SPISensor*)(sensor->instance))->spi);
free(sensor->instance);
if(--sensors_count == 0) {
unitemp_gpio_unlock(unitemp_gpio_getFromInt(2));
unitemp_gpio_unlock(unitemp_gpio_getFromInt(3));
unitemp_gpio_unlock(unitemp_gpio_getFromInt(5));
}
return status;
}
bool unitemp_spi_sensor_init(Sensor* sensor) {
return sensor->type->initializer(sensor);
}
bool unitemp_spi_sensor_deinit(Sensor* sensor) {
UNUSED(sensor);
return true;
}
UnitempStatus unitemp_spi_sensor_update(Sensor* sensor) {
return sensor->type->updater(sensor);
}

View file

@ -0,0 +1,66 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef UNITEMP_SPI
#define UNITEMP_SPI
#include "../unitemp.h"
#include <furi_hal_spi.h>
//Структура SPI датчика
typedef struct SPISensor {
//Указатель на интерфейс SPI
FuriHalSpiBusHandle* spi;
//Порт подключения CS
const GPIO* CS_pin;
} SPISensor;
/**
* @brief Выделение памяти для датчика с интерфейсом SPI
* @param sensor Указатель на датчик
* @param args Указатель на массив аргументов с параметрами датчика
* @return Истина если всё ок
*/
bool unitemp_spi_sensor_alloc(Sensor* sensor, char* args);
/**
* @brief Высвобождение памяти инстанса датчика
* @param sensor Указатель на датчик
*/
bool unitemp_spi_sensor_free(Sensor* sensor);
/**
* @brief Инициализации датчика с интерфейсом one wire
* @param sensor Указатель на датчик
* @return Истина если инициализация упспешная
*/
bool unitemp_spi_sensor_init(Sensor* sensor);
/**
* @brief Деинициализация датчика
* @param sensor Указатель на датчик
*/
bool unitemp_spi_sensor_deinit(Sensor* sensor);
/**
* @brief Обновить значение с датчка
* @param sensor Указатель на датчик
* @return Статус обновления
*/
UnitempStatus unitemp_spi_sensor_update(Sensor* sensor);
#endif

View file

@ -0,0 +1,93 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "MAX31855.h"
const SensorType MAX31855 = {
.typename = "MAX31855",
.altname = "MAX31855 (Thermocouple)",
.interface = &SPI,
.datatype = UT_TEMPERATURE,
.pollingInterval = 500,
.allocator = unitemp_MAX31855_alloc,
.mem_releaser = unitemp_MAX31855_free,
.initializer = unitemp_MAX31855_init,
.deinitializer = unitemp_MAX31855_deinit,
.updater = unitemp_MAX31855_update};
bool unitemp_MAX31855_alloc(Sensor* sensor, char* args) {
UNUSED(sensor);
UNUSED(args);
return true;
}
bool unitemp_MAX31855_free(Sensor* sensor) {
UNUSED(sensor);
return true;
}
bool unitemp_MAX31855_init(Sensor* sensor) {
SPISensor* instance = sensor->instance;
furi_hal_spi_bus_handle_init(instance->spi);
UNUSED(instance);
return true;
}
bool unitemp_MAX31855_deinit(Sensor* sensor) {
UNUSED(sensor);
return true;
}
UnitempStatus unitemp_MAX31855_update(Sensor* sensor) {
SPISensor* instance = sensor->instance;
furi_hal_spi_acquire(instance->spi);
furi_hal_gpio_write(instance->CS_pin->pin, false);
uint8_t buff[4] = {0};
furi_hal_spi_bus_rx(instance->spi, buff, 4, 0xFF);
furi_hal_spi_release(instance->spi);
uint32_t raw = (buff[0] << 24) | (buff[1] << 16) | (buff[2] << 8) | buff[3];
if(raw == 0xFFFFFFFF || raw == 0) return UT_SENSORSTATUS_TIMEOUT;
//Определение состояния термопары
uint8_t state = raw & 0b111;
//Обрыв
if(state == 0x01) {
UNITEMP_DEBUG("%s has thermocouple open circuit", sensor->name);
return UT_SENSORSTATUS_ERROR;
}
//Короткое замыкание к земле
if(state == 0x02) {
UNITEMP_DEBUG("%s has thermocouple short to GND", sensor->name);
return UT_SENSORSTATUS_ERROR;
}
//Короткое замыкание к питанию
if(state == 0x04) {
UNITEMP_DEBUG("%s has thermocouple short to VCC", sensor->name);
return UT_SENSORSTATUS_ERROR;
}
raw = (raw >> 16) & 0xFFFC;
sensor->temp = (int16_t)(raw) / 16.0f;
return UT_SENSORSTATUS_OK;
}

View file

@ -0,0 +1,65 @@
/*
Unitemp - Universal temperature reader
Copyright (C) 2022-2023 Victor Nikitchuk (https://github.com/quen0n)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef UNITEMP_MAX31855
#define UNITEMP_MAX31855
#include "../unitemp.h"
#include "../Sensors.h"
#include "../interfaces/SPISensor.h"
extern const SensorType MAX31855;
/**
* @brief Выделение памяти и установка начальных значений датчика MAX31855
*
* @param sensor Указатель на создаваемый датчик
* @return Истина при успехе
*/
bool unitemp_MAX31855_alloc(Sensor* sensor, char* args);
/**
* @brief Инициализации датчика MAX31855
*
* @param sensor Указатель на датчик
* @return Истина если инициализация упспешная
*/
bool unitemp_MAX31855_init(Sensor* sensor);
/**
* @brief Деинициализация датчика
*
* @param sensor Указатель на датчик
*/
bool unitemp_MAX31855_deinit(Sensor* sensor);
/**
* @brief Обновление значений из датчика
*
* @param sensor Указатель на датчик
* @return Статус обновления
*/
UnitempStatus unitemp_MAX31855_update(Sensor* sensor);
/**
* @brief Высвободить память датчика
*
* @param sensor Указатель на датчик
*/
bool unitemp_MAX31855_free(Sensor* sensor);
#endif

View file

@ -76,7 +76,7 @@ static void _draw_temperature(Canvas* canvas, Sensor* sensor, uint8_t x, uint8_t
app->buff[0] = '-'; app->buff[0] = '-';
offset = 1; offset = 1;
} }
snprintf((char*)(app->buff + offset), BUFF_SIZE, "%d", (int8_t)sensor->temp); snprintf((char*)(app->buff + offset), BUFF_SIZE, "%d", (int16_t)sensor->temp);
canvas_set_font(canvas, FontBigNumbers); canvas_set_font(canvas, FontBigNumbers);
canvas_draw_str_aligned( canvas_draw_str_aligned(
canvas, canvas,
@ -237,6 +237,7 @@ static void _draw_carousel_values(Canvas* canvas) {
canvas_draw_icon(canvas, 34, 23, frames[furi_get_tick() % 2250 / 750]); canvas_draw_icon(canvas, 34, 23, frames[furi_get_tick() % 2250 / 750]);
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
//TODO: Оптимизировать эту срань
if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &SINGLE_WIRE) { if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &SINGLE_WIRE) {
snprintf( snprintf(
app->buff, app->buff,
@ -256,6 +257,9 @@ static void _draw_carousel_values(Canvas* canvas) {
if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &I2C) { if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &I2C) {
snprintf(app->buff, BUFF_SIZE, "Waiting for module on I2C pins"); snprintf(app->buff, BUFF_SIZE, "Waiting for module on I2C pins");
} }
if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &SPI) {
snprintf(app->buff, BUFF_SIZE, "Waiting for module on SPI pins");
}
canvas_draw_str_aligned(canvas, 64, 19, AlignCenter, AlignCenter, app->buff); canvas_draw_str_aligned(canvas, 64, 19, AlignCenter, AlignCenter, app->buff);
return; return;
} }
@ -304,6 +308,8 @@ static void _draw_carousel_values(Canvas* canvas) {
break; break;
} }
} }
//TODO: Оптимизировать вывод информации
static void _draw_carousel_info(Canvas* canvas) { static void _draw_carousel_info(Canvas* canvas) {
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 10, 23, "Type:"); canvas_draw_str(canvas, 10, 23, "Type:");
@ -351,6 +357,25 @@ static void _draw_carousel_info(Canvas* canvas) {
->gpio->name); ->gpio->name);
} }
if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &SPI) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 10, 35, "MISO pin:");
canvas_draw_str(canvas, 10, 46, "CS pin:");
canvas_draw_str(canvas, 10, 58, "SCK pin:");
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(
canvas, 41, 23, unitemp_sensor_getActive(generalview_sensor_index)->type->typename);
canvas_draw_str(canvas, 60, 35, unitemp_gpio_getFromInt(3)->name);
canvas_draw_str(
canvas,
47,
46,
((SPISensor*)unitemp_sensor_getActive(generalview_sensor_index)->instance)
->CS_pin->name);
canvas_draw_str(canvas, 54, 58, unitemp_gpio_getFromInt(5)->name);
}
if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &I2C) { if(unitemp_sensor_getActive(generalview_sensor_index)->type->interface == &I2C) {
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 10, 35, "I2C addr:"); canvas_draw_str(canvas, 10, 35, "I2C addr:");

View file

@ -193,6 +193,12 @@ static void _gpio_change_callback(VariableItem* item) {
unitemp_gpio_getAviablePort(editable_sensor->type->interface, index, initial_gpio); unitemp_gpio_getAviablePort(editable_sensor->type->interface, index, initial_gpio);
variable_item_set_current_value_text(item, instance->gpio->name); variable_item_set_current_value_text(item, instance->gpio->name);
} }
if(editable_sensor->type->interface == &SPI) {
SPISensor* instance = editable_sensor->instance;
instance->CS_pin =
unitemp_gpio_getAviablePort(editable_sensor->type->interface, index, initial_gpio);
variable_item_set_current_value_text(item, instance->CS_pin->name);
}
if(editable_sensor->type->interface == &ONE_WIRE) { if(editable_sensor->type->interface == &ONE_WIRE) {
OneWireSensor* instance = editable_sensor->instance; OneWireSensor* instance = editable_sensor->instance;
instance->bus->gpio = instance->bus->gpio =
@ -296,12 +302,15 @@ void unitemp_SensorEdit_switch(Sensor* sensor) {
offset_buff, OFFSET_BUFF_SIZE, "%+1.1f", (double)(editable_sensor->temp_offset / 10.0)); offset_buff, OFFSET_BUFF_SIZE, "%+1.1f", (double)(editable_sensor->temp_offset / 10.0));
variable_item_set_current_value_text(temp_offset_item, offset_buff); variable_item_set_current_value_text(temp_offset_item, offset_buff);
//Порт подключения датчка (для one wire и single wire) //Порт подключения датчка (для one wire, SPI и single wire)
if(sensor->type->interface == &ONE_WIRE || sensor->type->interface == &SINGLE_WIRE) { if(sensor->type->interface == &ONE_WIRE || sensor->type->interface == &SINGLE_WIRE ||
sensor->type->interface == &SPI) {
if(sensor->type->interface == &ONE_WIRE) { if(sensor->type->interface == &ONE_WIRE) {
initial_gpio = ((OneWireSensor*)editable_sensor->instance)->bus->gpio; initial_gpio = ((OneWireSensor*)editable_sensor->instance)->bus->gpio;
} else { } else if(sensor->type->interface == &SINGLE_WIRE) {
initial_gpio = ((SingleWireSensor*)editable_sensor->instance)->gpio; initial_gpio = ((SingleWireSensor*)editable_sensor->instance)->gpio;
} else if(sensor->type->interface == &SPI) {
initial_gpio = ((SPISensor*)editable_sensor->instance)->CS_pin;
} }
uint8_t aviable_gpio_count = uint8_t aviable_gpio_count =

View file

@ -86,8 +86,8 @@ static void _enter_callback(void* context, uint32_t index) {
return; return;
} }
//Выбор первого доступного порта для датчика single wire //Выбор первого доступного порта для датчика single wire и SPI
if(type->interface == &SINGLE_WIRE) { if(type->interface == &SINGLE_WIRE || type->interface == &SPI) {
snprintf( snprintf(
args, args,
4, 4,

View file

@ -16,86 +16,90 @@
#define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1 #define FURI_HAL_SPEAKER_CHANNEL LL_TIM_CHANNEL_CH1
#define DMA_INSTANCE DMA1, LL_DMA_CHANNEL_1 #define DMA_INSTANCE DMA1, LL_DMA_CHANNEL_1
void wav_player_speaker_init(uint32_t sample_rate) void wav_player_speaker_init(uint32_t sample_rate) {
{ LL_TIM_InitTypeDef TIM_InitStruct = {0};
LL_TIM_InitTypeDef TIM_InitStruct = {0}; //TIM_InitStruct.Prescaler = 4;
//TIM_InitStruct.Prescaler = 4; TIM_InitStruct.Prescaler = 1;
TIM_InitStruct.Prescaler = 1; TIM_InitStruct.Autoreload =
TIM_InitStruct.Autoreload = 255; //in this fork used purely as PWM timer, the DMA now is triggered by SAMPLE_RATE_TIMER
255; //in this fork used purely as PWM timer, the DMA now is triggered by SAMPLE_RATE_TIMER LL_TIM_Init(FURI_HAL_SPEAKER_TIMER, &TIM_InitStruct);
LL_TIM_Init(FURI_HAL_SPEAKER_TIMER, &TIM_InitStruct);
LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0};
TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1;
TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE;
TIM_OC_InitStruct.CompareValue = 127; TIM_OC_InitStruct.CompareValue = 127;
LL_TIM_OC_Init(FURI_HAL_SPEAKER_TIMER, FURI_HAL_SPEAKER_CHANNEL, &TIM_OC_InitStruct); LL_TIM_OC_Init(FURI_HAL_SPEAKER_TIMER, FURI_HAL_SPEAKER_CHANNEL, &TIM_OC_InitStruct);
//====================================================== //======================================================
TIM_InitStruct.Prescaler = 0; TIM_InitStruct.Prescaler = 0;
//TIM_InitStruct.Autoreload = 1451; //64 000 000 / 1451 ~= 44100 Hz //TIM_InitStruct.Autoreload = 1451; //64 000 000 / 1451 ~= 44100 Hz
TIM_InitStruct.Autoreload = 64000000 / sample_rate; //to support various sample rates TIM_InitStruct.Autoreload = 64000000 / sample_rate; //to support various sample rates
LL_TIM_Init(SAMPLE_RATE_TIMER, &TIM_InitStruct); LL_TIM_Init(SAMPLE_RATE_TIMER, &TIM_InitStruct);
//LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0}; //LL_TIM_OC_InitTypeDef TIM_OC_InitStruct = {0};
TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1; TIM_OC_InitStruct.OCMode = LL_TIM_OCMODE_PWM1;
TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE; TIM_OC_InitStruct.OCState = LL_TIM_OCSTATE_ENABLE;
TIM_OC_InitStruct.CompareValue = 127; TIM_OC_InitStruct.CompareValue = 127;
LL_TIM_OC_Init(SAMPLE_RATE_TIMER, FURI_HAL_SPEAKER_CHANNEL, &TIM_OC_InitStruct); LL_TIM_OC_Init(SAMPLE_RATE_TIMER, FURI_HAL_SPEAKER_CHANNEL, &TIM_OC_InitStruct);
//========================================================= //=========================================================
//configuring PA6 pin as TIM16 output //configuring PA6 pin as TIM16 output
//furi_hal_gpio_init_ex(&gpio_ext_pa6, (GpioMode)GpioPullNo, (GpioPull)GpioModeAltFunctionPushPull, GpioSpeedVeryHigh, GpioAltFn14TIM16); //furi_hal_gpio_init_ex(&gpio_ext_pa6, (GpioMode)GpioPullNo, (GpioPull)GpioModeAltFunctionPushPull, GpioSpeedVeryHigh, GpioAltFn14TIM16);
//furi_hal_gpio_init_ex(&gpio_ext_pa6, (GpioMode)GpioPullNo, (GpioPull)GpioModeAltFunctionPushPull, GpioSpeedLow, GpioAltFn14TIM16); //furi_hal_gpio_init_ex(&gpio_ext_pa6, (GpioMode)GpioPullNo, (GpioPull)GpioModeAltFunctionPushPull, GpioSpeedLow, GpioAltFn14TIM16);
furi_hal_gpio_init_ex(&gpio_ext_pa6, GpioModeAltFunctionPushPull, GpioPullNo, GpioSpeedVeryHigh, GpioAltFn14TIM16); furi_hal_gpio_init_ex(
//furi_hal_gpio_init_simple(&gpio_ext_pa6, GpioModeOutputPushPull); &gpio_ext_pa6,
//furi_hal_gpio_write(&gpio_ext_pa6, false); GpioModeAltFunctionPushPull,
GpioPullNo,
GpioSpeedVeryHigh,
GpioAltFn14TIM16);
//furi_hal_gpio_init_simple(&gpio_ext_pa6, GpioModeOutputPushPull);
//furi_hal_gpio_write(&gpio_ext_pa6, false);
} }
void wav_player_speaker_start() { void wav_player_speaker_start() {
LL_TIM_EnableAllOutputs(FURI_HAL_SPEAKER_TIMER); LL_TIM_EnableAllOutputs(FURI_HAL_SPEAKER_TIMER);
LL_TIM_EnableCounter(FURI_HAL_SPEAKER_TIMER); LL_TIM_EnableCounter(FURI_HAL_SPEAKER_TIMER);
LL_TIM_EnableAllOutputs(SAMPLE_RATE_TIMER); LL_TIM_EnableAllOutputs(SAMPLE_RATE_TIMER);
LL_TIM_EnableCounter(SAMPLE_RATE_TIMER); LL_TIM_EnableCounter(SAMPLE_RATE_TIMER);
} }
void wav_player_speaker_stop() { void wav_player_speaker_stop() {
LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER); LL_TIM_DisableAllOutputs(FURI_HAL_SPEAKER_TIMER);
LL_TIM_DisableCounter(FURI_HAL_SPEAKER_TIMER); LL_TIM_DisableCounter(FURI_HAL_SPEAKER_TIMER);
LL_TIM_DisableAllOutputs(SAMPLE_RATE_TIMER); LL_TIM_DisableAllOutputs(SAMPLE_RATE_TIMER);
LL_TIM_DisableCounter(SAMPLE_RATE_TIMER); LL_TIM_DisableCounter(SAMPLE_RATE_TIMER);
} }
void wav_player_dma_init(uint32_t address, size_t size) { void wav_player_dma_init(uint32_t address, size_t size) {
uint32_t dma_dst = (uint32_t) & (FURI_HAL_SPEAKER_TIMER->CCR1); uint32_t dma_dst = (uint32_t) & (FURI_HAL_SPEAKER_TIMER->CCR1);
LL_DMA_ConfigAddresses(DMA_INSTANCE, address, dma_dst, LL_DMA_DIRECTION_MEMORY_TO_PERIPH); LL_DMA_ConfigAddresses(DMA_INSTANCE, address, dma_dst, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
LL_DMA_SetDataLength(DMA_INSTANCE, size); LL_DMA_SetDataLength(DMA_INSTANCE, size);
LL_DMA_SetPeriphRequest(DMA_INSTANCE, LL_DMAMUX_REQ_TIM2_UP); LL_DMA_SetPeriphRequest(DMA_INSTANCE, LL_DMAMUX_REQ_TIM2_UP);
LL_DMA_SetDataTransferDirection(DMA_INSTANCE, LL_DMA_DIRECTION_MEMORY_TO_PERIPH); LL_DMA_SetDataTransferDirection(DMA_INSTANCE, LL_DMA_DIRECTION_MEMORY_TO_PERIPH);
LL_DMA_SetChannelPriorityLevel(DMA_INSTANCE, LL_DMA_PRIORITY_VERYHIGH); LL_DMA_SetChannelPriorityLevel(DMA_INSTANCE, LL_DMA_PRIORITY_VERYHIGH);
LL_DMA_SetMode(DMA_INSTANCE, LL_DMA_MODE_CIRCULAR); LL_DMA_SetMode(DMA_INSTANCE, LL_DMA_MODE_CIRCULAR);
LL_DMA_SetPeriphIncMode(DMA_INSTANCE, LL_DMA_PERIPH_NOINCREMENT); LL_DMA_SetPeriphIncMode(DMA_INSTANCE, LL_DMA_PERIPH_NOINCREMENT);
LL_DMA_SetMemoryIncMode(DMA_INSTANCE, LL_DMA_MEMORY_INCREMENT); LL_DMA_SetMemoryIncMode(DMA_INSTANCE, LL_DMA_MEMORY_INCREMENT);
LL_DMA_SetPeriphSize(DMA_INSTANCE, LL_DMA_PDATAALIGN_HALFWORD); LL_DMA_SetPeriphSize(DMA_INSTANCE, LL_DMA_PDATAALIGN_HALFWORD);
LL_DMA_SetMemorySize(DMA_INSTANCE, LL_DMA_MDATAALIGN_HALFWORD); LL_DMA_SetMemorySize(DMA_INSTANCE, LL_DMA_MDATAALIGN_HALFWORD);
LL_DMA_EnableIT_TC(DMA_INSTANCE); LL_DMA_EnableIT_TC(DMA_INSTANCE);
LL_DMA_EnableIT_HT(DMA_INSTANCE); LL_DMA_EnableIT_HT(DMA_INSTANCE);
} }
void wav_player_dma_start() { void wav_player_dma_start() {
LL_DMA_EnableChannel(DMA_INSTANCE); LL_DMA_EnableChannel(DMA_INSTANCE);
LL_TIM_EnableDMAReq_UPDATE(SAMPLE_RATE_TIMER); LL_TIM_EnableDMAReq_UPDATE(SAMPLE_RATE_TIMER);
} }
void wav_player_dma_stop() { void wav_player_dma_stop() {
LL_DMA_DisableChannel(DMA_INSTANCE); LL_DMA_DisableChannel(DMA_INSTANCE);
} }

View file

@ -45,10 +45,6 @@ typedef struct {
uint8_t data[DATA_COUNT]; uint8_t data[DATA_COUNT];
} WavPlayerViewModel; } WavPlayerViewModel;
WavPlayerView* wav_player_view_alloc(); WavPlayerView* wav_player_view_alloc();
void wav_player_view_free(WavPlayerView* wav_view); void wav_player_view_free(WavPlayerView* wav_view);