updated protoview

This commit is contained in:
jbohack
2023-01-15 22:54:39 -05:00
parent 35befd07c4
commit debd850ccd
30 changed files with 1046 additions and 955 deletions
+2 -1
View File
@@ -14,8 +14,9 @@ and if the selected modulation is correct.
Other than that, ProtoView is able to decode a few interesting protocols:
* TPMS sensors: Renault, Toyota, Schrader, Citroen, Ford.
* Microchip HSC200/300/301 Keeloq protocol.
* Oregon thermometer protocol 2.
* PTxxxx/SCxxxx based remotes.
* PT2262, SC5262 based remotes.
* ... more will be implemented soon, hopefully. Send PRs :)
![ProtoView screenshot Renault TPMS data](/images/protoview_2.jpg)
+62 -62
View File
@@ -40,8 +40,8 @@ extern const SubGhzProtocolRegistry protoview_protocol_registry;
/* The callback actually just passes the control to the actual active
* view callback, after setting up basic stuff like cleaning the screen
* and setting color to black. */
static void render_callback(Canvas* const canvas, void* ctx) {
ProtoViewApp* app = ctx;
static void render_callback(Canvas *const canvas, void *ctx) {
ProtoViewApp *app = ctx;
/* Clear screen. */
canvas_set_color(canvas, ColorWhite);
@@ -51,63 +51,56 @@ static void render_callback(Canvas* const canvas, void* ctx) {
/* Call who is in charge right now. */
switch(app->current_view) {
case ViewRawPulses:
render_view_raw_pulses(canvas, app);
break;
case ViewInfo:
render_view_info(canvas, app);
break;
case ViewRawPulses: render_view_raw_pulses(canvas,app); break;
case ViewInfo: render_view_info(canvas,app); break;
case ViewFrequencySettings:
case ViewModulationSettings:
render_view_settings(canvas, app);
break;
case ViewDirectSampling:
render_view_direct_sampling(canvas, app);
break;
case ViewLast:
furi_crash(TAG " ViewLast selected");
break;
render_view_settings(canvas,app); 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
* in the while() loop of the app entry point function. */
static void input_callback(InputEvent* input_event, void* ctx) {
ProtoViewApp* app = ctx;
furi_message_queue_put(app->event_queue, input_event, FuriWaitForever);
static void input_callback(InputEvent* input_event, void* ctx)
{
ProtoViewApp *app = ctx;
furi_message_queue_put(app->event_queue,input_event,FuriWaitForever);
}
/* Called to switch view (when left/right is pressed). Handles
* changing the current view ID and calling the enter/exit view
* callbacks if needed. */
static void app_switch_view(ProtoViewApp* app, SwitchViewDirection dir) {
static void app_switch_view(ProtoViewApp *app, SwitchViewDirection dir) {
ProtoViewCurrentView old = app->current_view;
if(dir == AppNextView) {
if (dir == AppNextView) {
app->current_view++;
if(app->current_view == ViewLast) app->current_view = 0;
} else if(dir == AppPrevView) {
if(app->current_view == 0)
app->current_view = ViewLast - 1;
if (app->current_view == ViewLast) app->current_view = 0;
} else if (dir == AppPrevView) {
if (app->current_view == 0)
app->current_view = ViewLast-1;
else
app->current_view--;
}
ProtoViewCurrentView new = app->current_view;
/* Call the enter/exit view callbacks if needed. */
if(old == ViewDirectSampling) view_exit_direct_sampling(app);
if(new == ViewDirectSampling) view_enter_direct_sampling(app);
if (old == ViewDirectSampling) view_exit_direct_sampling(app);
if (new == ViewDirectSampling) view_enter_direct_sampling(app);
/* The frequency/modulation settings are actually a single view:
* as long as the user stays between the two modes of this view we
* don't need to call the exit-view callback. */
if((old == ViewFrequencySettings && new != ViewModulationSettings) ||
(old == ViewModulationSettings && new != ViewFrequencySettings))
if ((old == ViewFrequencySettings && new != ViewModulationSettings) ||
(old == ViewModulationSettings && new != ViewFrequencySettings))
view_exit_settings(app);
}
/* Allocate the application state and initialize a number of stuff.
* This is called in the entry point to create the application state. */
ProtoViewApp* protoview_app_alloc() {
ProtoViewApp* app = malloc(sizeof(ProtoViewApp));
ProtoViewApp *app = malloc(sizeof(ProtoViewApp));
// Init shared data structures
RawSamples = raw_samples_alloc();
@@ -149,14 +142,17 @@ ProtoViewApp* protoview_app_alloc() {
app->txrx->environment = subghz_environment_alloc();
subghz_environment_set_protocol_registry(
app->txrx->environment, (void*)&protoview_protocol_registry);
app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment);
subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable);
app->txrx->receiver =
subghz_receiver_alloc_init(app->txrx->environment);
subghz_receiver_set_filter(app->txrx->receiver,
SubGhzProtocolFlag_Decodable);
subghz_worker_set_overrun_callback(
app->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset);
app->txrx->worker,
(SubGhzWorkerOverrunCallback)subghz_receiver_reset);
subghz_worker_set_pair_callback(
app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
subghz_worker_set_context(app->txrx->worker, app->txrx->receiver);
app->frequency = subghz_setting_get_default_frequency(app->setting);
app->modulation = 0; /* Defaults to ProtoViewModulations[0]. */
@@ -169,7 +165,7 @@ ProtoViewApp* protoview_app_alloc() {
/* 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
* even if we forget to free something here. */
void protoview_app_free(ProtoViewApp* app) {
void protoview_app_free(ProtoViewApp *app) {
furi_assert(app);
// Put CC1101 on sleep.
@@ -187,7 +183,7 @@ void protoview_app_free(ProtoViewApp* app) {
subghz_setting_free(app->setting);
// Worker stuff.
if(!app->txrx->debug_timer_sampling) {
if (!app->txrx->debug_timer_sampling) {
subghz_receiver_free(app->txrx->receiver);
subghz_environment_free(app->txrx->environment);
subghz_worker_free(app->txrx->worker);
@@ -205,8 +201,8 @@ void protoview_app_free(ProtoViewApp* app) {
/* Called periodically. Do signal processing here. Data we process here
* will be later displayed by the render callback. The side effect of this
* function is to scan for signals and set DetectedSamples. */
static void timer_callback(void* ctx) {
ProtoViewApp* app = ctx;
static void timer_callback(void *ctx) {
ProtoViewApp *app = ctx;
uint32_t delta, lastidx = app->signal_last_scan_idx;
/* scan_for_signal(), called by this function, deals with a
@@ -214,22 +210,22 @@ static void timer_callback(void* ctx) {
* 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
* can avoid scanning too many times to just find the same data. */
if(lastidx < RawSamples->idx) {
if (lastidx < RawSamples->idx) {
delta = RawSamples->idx - lastidx;
} else {
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;
scan_for_signal(app);
}
int32_t protoview_app_entry(void* p) {
UNUSED(p);
ProtoViewApp* app = protoview_app_alloc();
ProtoViewApp *app = protoview_app_alloc();
/* 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);
/* Start listening to signals immediately. */
@@ -244,57 +240,60 @@ int32_t protoview_app_entry(void* p) {
InputEvent input;
while(app->running) {
FuriStatus qstat = furi_message_queue_get(app->event_queue, &input, 100);
if(qstat == FuriStatusOk) {
if(DEBUG_MSG)
FURI_LOG_E(TAG, "Main Loop - Input: type %d key %u", input.type, input.key);
if (qstat == FuriStatusOk) {
if (DEBUG_MSG) FURI_LOG_E(TAG, "Main Loop - Input: type %d key %u",
input.type, input.key);
/* Handle navigation here. Then handle view-specific inputs
* in the view specific handling function. */
if(input.type == InputTypeShort && input.key == InputKeyBack) {
if (input.type == InputTypeShort &&
input.key == InputKeyBack)
{
/* Exit the app. */
app->running = 0;
} else if(input.type == InputTypeShort && input.key == InputKeyRight) {
} else if (input.type == InputTypeShort &&
input.key == InputKeyRight)
{
/* Go to the next view. */
app_switch_view(app, AppNextView);
} else if(input.type == InputTypeShort && input.key == InputKeyLeft) {
app_switch_view(app,AppNextView);
} else if (input.type == InputTypeShort &&
input.key == InputKeyLeft)
{
/* Go to the previous view. */
app_switch_view(app, AppPrevView);
app_switch_view(app,AppPrevView);
} else {
/* This is where we pass the control to the currently
* active view input processing. */
switch(app->current_view) {
case ViewRawPulses:
process_input_raw_pulses(app, input);
process_input_raw_pulses(app,input);
break;
case ViewInfo:
process_input_info(app, input);
process_input_info(app,input);
break;
case ViewFrequencySettings:
case ViewModulationSettings:
process_input_settings(app, input);
process_input_settings(app,input);
break;
case ViewDirectSampling:
process_input_direct_sampling(app, input);
break;
case ViewLast:
furi_crash(TAG " ViewLast selected");
process_input_direct_sampling(app,input);
break;
case ViewLast: furi_crash(TAG " ViewLast selected"); break;
}
}
} else {
/* Useful to understand if the app is still alive when it
* does not respond because of bugs. */
if(DEBUG_MSG) {
static int c = 0;
c++;
if(!(c % 20)) FURI_LOG_E(TAG, "Loop timeout");
if (DEBUG_MSG) {
static int c = 0; c++;
if (!(c % 20)) FURI_LOG_E(TAG, "Loop timeout");
}
}
view_port_update(app->view_port);
}
/* 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.");
radio_rx_end(app);
radio_sleep(app);
@@ -304,3 +303,4 @@ int32_t protoview_app_entry(void* p) {
protoview_app_free(app);
return 0;
}
+50 -71
View File
@@ -48,12 +48,15 @@ typedef enum {
} ProtoViewCurrentView;
/* Used by app_switch_view() */
typedef enum { AppNextView, AppPrevView } SwitchViewDirection;
typedef enum {
AppNextView,
AppPrevView
} SwitchViewDirection;
typedef struct {
const char* name;
const char *name;
FuriHalSubGhzPreset preset;
uint8_t* custom;
uint8_t *custom;
} ProtoViewModulation;
extern ProtoViewModulation ProtoViewModulations[]; /* In app_subghz.c */
@@ -62,19 +65,19 @@ extern ProtoViewModulation ProtoViewModulations[]; /* In app_subghz.c */
* It receives data and we get our protocol "feed" callback called
* with the level (1 or 0) and duration. */
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
radio with the right parameters. */
SubGhzWorker* worker; /* Our background worker. */
SubGhzWorker* worker; /* Our background worker. */
SubGhzEnvironment* environment;
SubGhzReceiver* receiver;
TxRxState txrx_state; /* Receiving, idle or sleeping? */
/* 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. */
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
handler. */
};
@@ -94,43 +97,43 @@ typedef struct ProtoViewMsgInfo {
char info2[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 2. */
char info3[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 3. */
char info4[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 4. */
uint64_t len; /* Bits consumed from the stream. */
uint64_t len; /* Bits consumed from the stream. */
} ProtoViewMsgInfo;
struct ProtoViewApp {
/* GUI */
Gui* gui;
ViewPort* view_port; /* We just use a raw viewport and we render
Gui *gui;
ViewPort *view_port; /* We just use a raw viewport and we render
everything into the low level canvas. */
ProtoViewCurrentView current_view; /* Active view ID. */
FuriMessageQueue* event_queue; /* Keypress events go here. */
ProtoViewCurrentView current_view; /* Active view ID. */
FuriMessageQueue *event_queue; /* Keypress events go here. */
/* Radio related. */
ProtoViewTxRx* txrx; /* Radio state. */
SubGhzSetting* setting; /* A list of valid frequencies. */
ProtoViewTxRx *txrx; /* Radio state. */
SubGhzSetting *setting; /* A list of valid frequencies. */
/* 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_last_scan_idx; /* Index of the buffer last time we
performed the scan. */
bool signal_decoded; /* Was the current signal decoded? */
bool signal_decoded; /* Was the current signal decoded? */
ProtoViewMsgInfo signal_info; /* Decoded message, if signal_decoded true. */
bool direct_sampling_enabled; /* This special view needs an explicit
acknowledge to work. */
/* Raw view apps state. */
uint32_t us_scale; /* microseconds per pixel. */
uint32_t signal_offset; /* Long press left/right panning in raw view. */
uint32_t us_scale; /* microseconds per pixel. */
uint32_t signal_offset; /* Long press left/right panning in raw view. */
/* Configuration view app state. */
uint32_t frequency; /* Current frequency. */
uint8_t modulation; /* Current modulation ID, array index in the
uint32_t frequency; /* Current frequency. */
uint8_t modulation; /* Current modulation ID, array index in the
ProtoViewModulations table. */
};
typedef struct ProtoViewDecoder {
const char* name; /* Protocol name. */
const char *name; /* Protocol name. */
/* The decode function takes a buffer that is actually a bitmap, with
* high and low levels represented as 0 and 1. The number of high/low
* pulses represented by the bitmap is passed as the 'numbits' argument,
@@ -138,7 +141,7 @@ typedef struct ProtoViewDecoder {
* 'bits'. So 'numbytes' is mainly useful to pass as argument to other
* functions that perform bit extraction with bound checking, such as
* bitmap_get() and so forth. */
bool (*decode)(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info);
bool (*decode)(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info);
} ProtoViewDecoder;
extern RawSamplesBuffer *RawSamples, *DetectedSamples;
@@ -149,61 +152,37 @@ uint32_t radio_rx(ProtoViewApp* app);
void radio_idle(ProtoViewApp* app);
void radio_rx_end(ProtoViewApp* app);
void radio_sleep(ProtoViewApp* app);
void raw_sampling_worker_start(ProtoViewApp* app);
void raw_sampling_worker_stop(ProtoViewApp* app);
void raw_sampling_worker_start(ProtoViewApp *app);
void raw_sampling_worker_stop(ProtoViewApp *app);
/* signal.c */
uint32_t duration_delta(uint32_t a, uint32_t b);
void reset_current_signal(ProtoViewApp* app);
void scan_for_signal(ProtoViewApp* app);
bool bitmap_get(uint8_t* b, uint32_t blen, uint32_t bitpos);
void bitmap_set(uint8_t* b, uint32_t blen, uint32_t bitpos, bool val);
void bitmap_set_pattern(uint8_t* b, uint32_t blen, const char* pat);
void bitmap_invert_bytes_bits(uint8_t* p, uint32_t len);
bool bitmap_match_bits(uint8_t* b, uint32_t blen, uint32_t bitpos, const char* bits);
uint32_t bitmap_seek_bits(
uint8_t* b,
uint32_t blen,
uint32_t startpos,
uint32_t maxbits,
const char* bits);
uint32_t convert_from_line_code(
uint8_t* buf,
uint64_t buflen,
uint8_t* bits,
uint32_t len,
uint32_t offset,
const char* zero_pattern,
const char* one_pattern);
uint32_t convert_from_diff_manchester(
uint8_t* buf,
uint64_t buflen,
uint8_t* bits,
uint32_t len,
uint32_t off,
bool previous);
void reset_current_signal(ProtoViewApp *app);
void scan_for_signal(ProtoViewApp *app);
bool bitmap_get(uint8_t *b, uint32_t blen, uint32_t bitpos);
void bitmap_set(uint8_t *b, uint32_t blen, uint32_t bitpos, bool val);
void bitmap_set_pattern(uint8_t *b, uint32_t blen, const char *pat);
void bitmap_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);
/* view_*.c */
void render_view_raw_pulses(Canvas* const canvas, ProtoViewApp* app);
void process_input_raw_pulses(ProtoViewApp* app, InputEvent input);
void render_view_settings(Canvas* const canvas, ProtoViewApp* app);
void process_input_settings(ProtoViewApp* app, InputEvent input);
void render_view_info(Canvas* const canvas, ProtoViewApp* app);
void process_input_info(ProtoViewApp* app, InputEvent input);
void render_view_direct_sampling(Canvas* const canvas, ProtoViewApp* app);
void process_input_direct_sampling(ProtoViewApp* app, InputEvent input);
void view_enter_direct_sampling(ProtoViewApp* app);
void view_exit_direct_sampling(ProtoViewApp* app);
void view_exit_settings(ProtoViewApp* app);
void render_view_raw_pulses(Canvas *const canvas, ProtoViewApp *app);
void process_input_raw_pulses(ProtoViewApp *app, InputEvent input);
void render_view_settings(Canvas *const canvas, ProtoViewApp *app);
void process_input_settings(ProtoViewApp *app, InputEvent input);
void render_view_info(Canvas *const canvas, ProtoViewApp *app);
void process_input_info(ProtoViewApp *app, InputEvent input);
void render_view_direct_sampling(Canvas *const canvas, ProtoViewApp *app);
void process_input_direct_sampling(ProtoViewApp *app, InputEvent input);
void view_enter_direct_sampling(ProtoViewApp *app);
void view_exit_direct_sampling(ProtoViewApp *app);
void view_exit_settings(ProtoViewApp *app);
/* ui.c */
void canvas_draw_str_with_border(
Canvas* canvas,
uint8_t x,
uint8_t y,
const char* str,
Color text_color,
Color border_color);
void canvas_draw_str_with_border(Canvas* canvas, uint8_t x, uint8_t y, const char* str, Color text_color, Color border_color);
/* 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);
+22 -23
View File
@@ -8,15 +8,15 @@
#include "app_buffer.h"
/* Allocate and initialize a samples buffer. */
RawSamplesBuffer* raw_samples_alloc(void) {
RawSamplesBuffer* buf = malloc(sizeof(*buf));
RawSamplesBuffer *raw_samples_alloc(void) {
RawSamplesBuffer *buf = malloc(sizeof(*buf));
buf->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
raw_samples_reset(buf);
return buf;
}
/* 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);
free(s);
}
@@ -24,49 +24,48 @@ void raw_samples_free(RawSamplesBuffer* s) {
/* 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
* when one wants to reset the whole buffer of samples. */
void raw_samples_reset(RawSamplesBuffer* s) {
furi_mutex_acquire(s->mutex, FuriWaitForever);
void raw_samples_reset(RawSamplesBuffer *s) {
furi_mutex_acquire(s->mutex,FuriWaitForever);
s->total = RAW_SAMPLES_NUM;
s->idx = 0;
s->short_pulse_dur = 0;
memset(s->level, 0, sizeof(s->level));
memset(s->dur, 0, sizeof(s->dur));
memset(s->samples,0,sizeof(s->samples));
furi_mutex_release(s->mutex);
}
/* Set the raw sample internal index so that what is currently at
* offset 'offset', will appear to be at 0 index. */
void raw_samples_center(RawSamplesBuffer* s, uint32_t offset) {
s->idx = (s->idx + offset) % RAW_SAMPLES_NUM;
void raw_samples_center(RawSamplesBuffer *s, uint32_t offset) {
s->idx = (s->idx+offset) % RAW_SAMPLES_NUM;
}
/* Add the specified sample in the circular buffer. */
void raw_samples_add(RawSamplesBuffer* s, bool level, uint32_t dur) {
furi_mutex_acquire(s->mutex, FuriWaitForever);
s->level[s->idx] = level;
s->dur[s->idx] = dur;
s->idx = (s->idx + 1) % RAW_SAMPLES_NUM;
void raw_samples_add(RawSamplesBuffer *s, bool level, uint32_t dur) {
furi_mutex_acquire(s->mutex,FuriWaitForever);
s->samples[s->idx].level = level;
s->samples[s->idx].dur = dur;
s->idx = (s->idx+1) % RAW_SAMPLES_NUM;
furi_mutex_release(s->mutex);
}
/* 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. */
void raw_samples_get(RawSamplesBuffer* s, uint32_t idx, bool* level, uint32_t* dur) {
furi_mutex_acquire(s->mutex, FuriWaitForever);
void raw_samples_get(RawSamplesBuffer *s, uint32_t idx, bool *level, uint32_t *dur)
{
furi_mutex_acquire(s->mutex,FuriWaitForever);
idx = (s->idx + idx) % RAW_SAMPLES_NUM;
*level = s->level[idx];
*dur = s->dur[idx];
*level = s->samples[idx].level;
*dur = s->samples[idx].dur;
furi_mutex_release(s->mutex);
}
/* Copy one buffer to the other, including current index. */
void raw_samples_copy(RawSamplesBuffer* dst, RawSamplesBuffer* src) {
furi_mutex_acquire(src->mutex, FuriWaitForever);
furi_mutex_acquire(dst->mutex, FuriWaitForever);
void raw_samples_copy(RawSamplesBuffer *dst, RawSamplesBuffer *src) {
furi_mutex_acquire(src->mutex,FuriWaitForever);
furi_mutex_acquire(dst->mutex,FuriWaitForever);
dst->idx = src->idx;
dst->short_pulse_dur = src->short_pulse_dur;
memcpy(dst->level, src->level, sizeof(dst->level));
memcpy(dst->dur, src->dur, sizeof(dst->dur));
memcpy(dst->samples,src->samples,sizeof(dst->samples));
furi_mutex_release(src->mutex);
furi_mutex_release(dst->mutex);
}
+14 -14
View File
@@ -4,16 +4,16 @@
/* Our circular buffer of raw samples, used in order to display
* 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
the range, and division is slow. */
typedef struct RawSamplesBuffer {
FuriMutex* mutex;
uint8_t level[RAW_SAMPLES_NUM];
uint32_t dur[RAW_SAMPLES_NUM];
uint32_t idx; /* Current idx (next to write). */
FuriMutex *mutex;
struct {
uint16_t level:1;
uint16_t dur:15;
} samples[RAW_SAMPLES_NUM];
uint32_t idx; /* Current idx (next to write). */
uint32_t total; /* Total samples: same as RAW_SAMPLES_NUM, we provide
this field for a cleaner interface with the user, but
we always use RAW_SAMPLES_NUM when taking the modulo so
@@ -22,10 +22,10 @@ typedef struct RawSamplesBuffer {
uint32_t short_pulse_dur; /* Duration of the shortest pulse. */
} RawSamplesBuffer;
RawSamplesBuffer* raw_samples_alloc(void);
void raw_samples_reset(RawSamplesBuffer* s);
void raw_samples_center(RawSamplesBuffer* s, uint32_t offset);
void raw_samples_add(RawSamplesBuffer* s, bool level, uint32_t dur);
void raw_samples_get(RawSamplesBuffer* s, uint32_t idx, bool* level, uint32_t* dur);
void raw_samples_copy(RawSamplesBuffer* dst, RawSamplesBuffer* src);
void raw_samples_free(RawSamplesBuffer* s);
RawSamplesBuffer *raw_samples_alloc(void);
void raw_samples_reset(RawSamplesBuffer *s);
void raw_samples_center(RawSamplesBuffer *s, uint32_t offset);
void raw_samples_add(RawSamplesBuffer *s, bool level, uint32_t dur);
void raw_samples_get(RawSamplesBuffer *s, uint32_t idx, bool *level, uint32_t *dur);
void raw_samples_copy(RawSamplesBuffer *dst, RawSamplesBuffer *src);
void raw_samples_free(RawSamplesBuffer *s);
+24 -21
View File
@@ -9,8 +9,8 @@
#include <furi_hal_spi.h>
#include <furi_hal_interrupt.h>
void raw_sampling_worker_start(ProtoViewApp* app);
void raw_sampling_worker_stop(ProtoViewApp* app);
void raw_sampling_worker_start(ProtoViewApp *app);
void raw_sampling_worker_stop(ProtoViewApp *app);
ProtoViewModulation ProtoViewModulations[] = {
{"OOK 650Khz", FuriHalSubGhzPresetOok650Async, NULL},
@@ -19,9 +19,10 @@ ProtoViewModulation ProtoViewModulations[] = {
{"2FSK 47.6Khz", FuriHalSubGhzPreset2FSKDev476Async, NULL},
{"MSK", FuriHalSubGhzPresetMSK99_97KbAsync, NULL},
{"GFSK", FuriHalSubGhzPresetGFSK9_99KbAsync, NULL},
{"TPMS 1 (FSK)", 0, (uint8_t*)protoview_subghz_tpms1_async_regs},
{"TPMS 2 (FSK)", 0, (uint8_t*)protoview_subghz_tpms2_async_regs},
{"TPMS 3 (FSK)", 0, (uint8_t*)protoview_subghz_tpms3_async_regs},
{"TPMS 1 (FSK)", 0, (uint8_t*)protoview_subghz_tpms1_fsk_async_regs},
{"TPMS 2 (OOK)", 0, (uint8_t*)protoview_subghz_tpms2_ook_async_regs},
{"TPMS 3 (FSK)", 0, (uint8_t*)protoview_subghz_tpms3_fsk_async_regs},
{"TPMS 4 (FSK)", 0, (uint8_t*)protoview_subghz_tpms4_fsk_async_regs},
{NULL, 0, NULL} /* End of list sentinel. */
};
@@ -37,7 +38,7 @@ void radio_begin(ProtoViewApp* app) {
/* The CC1101 preset can be either one of the standard presets, if
* the modulation "custom" field is NULL, or a custom preset we
* defined in custom_presets.h. */
if(ProtoViewModulations[app->modulation].custom == NULL)
if (ProtoViewModulations[app->modulation].custom == NULL)
furi_hal_subghz_load_preset(ProtoViewModulations[app->modulation].preset);
else
furi_hal_subghz_load_custom_preset(ProtoViewModulations[app->modulation].custom);
@@ -49,10 +50,10 @@ void radio_begin(ProtoViewApp* app) {
uint32_t radio_rx(ProtoViewApp* app) {
furi_assert(app);
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. */
uint32_t value = furi_hal_subghz_set_frequency_and_path(app->frequency);
@@ -60,8 +61,10 @@ uint32_t radio_rx(ProtoViewApp* app) {
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
furi_hal_subghz_flush_rx();
furi_hal_subghz_rx();
if(!app->txrx->debug_timer_sampling) {
furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, app->txrx->worker);
if (!app->txrx->debug_timer_sampling) {
furi_hal_subghz_start_async_rx(subghz_worker_rx_callback,
app->txrx->worker);
subghz_worker_start(app->txrx->worker);
} else {
raw_sampling_worker_start(app);
@@ -73,8 +76,8 @@ uint32_t radio_rx(ProtoViewApp* app) {
/* Stop subghz worker (if active), put radio on idle state. */
void radio_rx_end(ProtoViewApp* app) {
furi_assert(app);
if(app->txrx->txrx_state == TxRxStateRx) {
if(!app->txrx->debug_timer_sampling) {
if (app->txrx->txrx_state == TxRxStateRx) {
if (!app->txrx->debug_timer_sampling) {
if(subghz_worker_is_running(app->txrx->worker)) {
subghz_worker_stop(app->txrx->worker);
furi_hal_subghz_stop_async_rx();
@@ -90,7 +93,7 @@ void radio_rx_end(ProtoViewApp* app) {
/* Put radio on sleep. */
void radio_sleep(ProtoViewApp* 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.
* Stop the RX subsystems first. */
radio_rx_end(app);
@@ -106,15 +109,15 @@ void radio_sleep(ProtoViewApp* app) {
* Flipper system.
* ===========================================================================*/
void protoview_timer_isr(void* ctx) {
ProtoViewApp* app = ctx;
void protoview_timer_isr(void *ctx) {
ProtoViewApp *app = ctx;
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 dur = now - app->txrx->last_g0_change_time;
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);
app->txrx->last_g0_value = level;
app->txrx->last_g0_change_time = now;
@@ -122,13 +125,13 @@ void protoview_timer_isr(void* ctx) {
LL_TIM_ClearFlag_UPDATE(TIM2);
}
void raw_sampling_worker_start(ProtoViewApp* app) {
void raw_sampling_worker_start(ProtoViewApp *app) {
UNUSED(app);
LL_TIM_InitTypeDef tim_init = {
.Prescaler = 63, /* CPU frequency is ~64Mhz. */
.Prescaler = 63, /* CPU frequency is ~64Mhz. */
.CounterMode = LL_TIM_COUNTERMODE_UP,
.Autoreload = 5, /* Sample every 5 us */
.Autoreload = 5, /* Sample every 5 us */
};
LL_TIM_Init(TIM2, &tim_init);
@@ -141,7 +144,7 @@ void raw_sampling_worker_start(ProtoViewApp* app) {
FURI_LOG_E(TAG, "Timer enabled");
}
void raw_sampling_worker_stop(ProtoViewApp* app) {
void raw_sampling_worker_stop(ProtoViewApp *app) {
UNUSED(app);
FURI_CRITICAL_ENTER();
LL_TIM_DisableCounter(TIM2);
+5 -4
View File
@@ -3,13 +3,14 @@
/* CRC8 with the specified initialization value 'init' and
* 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;
size_t i, j;
for(i = 0; i < len; i++) {
for (i = 0; i < len; i++) {
crc ^= data[i];
for(j = 0; j < 8; j++) {
if((crc & 0x80) != 0)
for (j = 0; j < 8; j++) {
if ((crc & 0x80) != 0)
crc = (uint8_t)((crc << 1) ^ poly);
else
crc <<= 1;
+64 -13
View File
@@ -1,8 +1,8 @@
#include <cc1101.h>
/* This is how to configure registers MDMCFG3 and MDMCFG4.
/* ========================== DATA RATE SETTINGS ===============================
*
* Data rate kBaud setting:
* This is how to configure registers MDMCFG3 and MDMCFG4.
*
* MDMCFG3 is the data rate mantissa, the exponent is in MDMCFG4,
* last 4 bits of the register.
@@ -15,6 +15,8 @@
*
* ((256+34)*(2^12))/(2^28)*26000000 = 115051.2688000000, that is 115KBaud
*
* ============================ BANDWIDTH FILTER ===============================
*
* Bandwidth filter setting:
*
* BW filter as just 16 possibilities depending on how the first nibble
@@ -39,6 +41,8 @@
* e 68 khz
* f 58 khz
*
* ============================== FSK DEVIATION ================================
*
* FSK deviation is controlled by the DEVIATION register. In Ruby:
*
* dev = (26000000.0/2**17)*(8+(deviation&7))*(2**(deviation>>4&7))
@@ -58,7 +62,7 @@
*/
/* 20 KBaud, 2FSK, 28.56 kHz deviation, 325 Khz bandwidth filter. */
static uint8_t protoview_subghz_tpms1_async_regs[][2] = {
static uint8_t protoview_subghz_tpms1_fsk_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
@@ -72,8 +76,7 @@ static uint8_t protoview_subghz_tpms1_async_regs[][2] = {
// // Modem Configuration
{CC1101_MDMCFG0, 0x00},
{CC1101_MDMCFG1, 0x02},
{CC1101_MDMCFG2,
0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized). Other code reading TPMS uses GFSK, but should be the same when in RX mode.
{CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized). Other code reading TPMS uses GFSK, but should be the same when in RX mode.
{CC1101_MDMCFG3, 0x93}, // Data rate is 20kBaud
{CC1101_MDMCFG4, 0x59}, // Rx bandwidth filter is 325 kHz
{CC1101_DEVIATN, 0x41}, // Deviation 28.56 kHz
@@ -103,8 +106,55 @@ static uint8_t protoview_subghz_tpms1_async_regs[][2] = {
{0, 0},
};
/* 40 KBaud, 2FSK, 19 kHz deviation, 102 Khz bandwidth filter. */
static uint8_t protoview_subghz_tpms2_async_regs[][2] = {
/* This is like the default Flipper OOK 640Khz bandwidth preset, but
* the bandwidth is changed to 10kBaud to accomodate TPMS frequency. */
static const uint8_t protoview_subghz_tpms2_ook_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
/* FIFO and internals */
{CC1101_FIFOTHR, 0x07}, // The only important bit is ADC_RETENTION
/* Packet engine */
{CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening
/* Frequency Synthesizer Control */
{CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz
// Modem Configuration
{CC1101_MDMCFG0, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG1, 0x00}, // Channel spacing is 25kHz
{CC1101_MDMCFG2, 0x30}, // Format ASK/OOK, No preamble/sync
{CC1101_MDMCFG3, 0x93}, // Data rate is 10kBaud
{CC1101_MDMCFG4, 0x18}, // Rx BW filter is 650.000kHz
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
/* Frequency Offset Compensation Configuration */
{CC1101_FOCCFG,
0x18}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off
/* Automatic Gain Control */
{CC1101_AGCCTRL0,
0x91}, // 10 - Medium hysteresis, medium asymmetric dead zone, medium gain ; 01 - 16 samples agc; 00 - Normal AGC, 01 - 8dB boundary
{CC1101_AGCCTRL1,
0x0}, // 0; 0 - LNA 2 gain is decreased to minimum before decreasing LNA gain; 00 - Relative carrier sense threshold disabled; 0000 - RSSI to MAIN_TARGET
{CC1101_AGCCTRL2, 0x07}, // 00 - DVGA all; 000 - MAX LNA+LNA2; 111 - MAIN_TARGET 42 dB
/* Wake on radio and timeouts control */
{CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours
/* Frontend configuration */
{CC1101_FREND0, 0x11}, // Adjusts current TX LO buffer + high is PATABLE[1]
{CC1101_FREND1, 0xB6}, //
/* End */
{0, 0},
};
/* 40 KBaud, 2FSK, 28 kHz deviation, 270 Khz bandwidth filter. */
static uint8_t protoview_subghz_tpms3_fsk_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
@@ -118,11 +168,10 @@ static uint8_t protoview_subghz_tpms2_async_regs[][2] = {
// // Modem Configuration
{CC1101_MDMCFG0, 0x00},
{CC1101_MDMCFG1, 0x02},
{CC1101_MDMCFG2,
0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized). Other code reading TPMS uses GFSK, but should be the same when in RX mode.
{CC1101_MDMCFG2, 0x04}, // Format 2-FSK/FM, No preamble/sync, Disable (current optimized). Other code reading TPMS uses GFSK, but should be the same when in RX mode.
{CC1101_MDMCFG3, 0x93}, // Data rate is 40kBaud
{CC1101_MDMCFG4, 0x6A}, // 6 = BW filter 270kHz, A = Data rate exp
{CC1101_DEVIATN, 0x41}, // Deviation 19.042 kHz
{CC1101_DEVIATN, 0x41}, // Deviation 28kHz
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
@@ -149,8 +198,8 @@ static uint8_t protoview_subghz_tpms2_async_regs[][2] = {
{0, 0},
};
/* Parameters that should work well for the TPMS PVM C210 sensor. */
static uint8_t protoview_subghz_tpms3_async_regs[][2] = {
/* FSK 19k dev, 325 Khz filter, 20kBaud. Works well with Toyota. */
static uint8_t protoview_subghz_tpms4_fsk_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
@@ -167,7 +216,7 @@ static uint8_t protoview_subghz_tpms3_async_regs[][2] = {
{CC1101_MDMCFG2, 0x10}, // GFSK without any other check
{CC1101_MDMCFG3, 0x93}, // Data rate is 20kBaud
{CC1101_MDMCFG4, 0x59}, // Rx bandwidth filter is 325 kHz
{CC1101_DEVIATN, 0x34}, // Deviation 19.04 Khz, works well with TPMS
{CC1101_DEVIATN, 0x34}, // Deviation 19.04 Khz.
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
@@ -191,3 +240,5 @@ static uint8_t protoview_subghz_tpms3_async_regs[][2] = {
/* End */
{0, 0},
};
+11 -6
View File
@@ -14,7 +14,7 @@ const SubGhzProtocol subghz_protocol_protoview;
/* The feed() method puts data in the RawSamples global (protected by
* a mutex). */
extern RawSamplesBuffer* RawSamples;
extern RawSamplesBuffer *RawSamples;
/* 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
@@ -26,7 +26,8 @@ typedef struct SubGhzProtocolDecoderprotoview {
void* subghz_protocol_decoder_protoview_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderprotoview* instance = malloc(sizeof(SubGhzProtocolDecoderprotoview));
SubGhzProtocolDecoderprotoview* instance =
malloc(sizeof(SubGhzProtocolDecoderprotoview));
instance->base.protocol = &subghz_protocol_protoview;
return instance;
}
@@ -65,7 +66,8 @@ uint8_t subghz_protocol_decoder_protoview_get_hash_data(void* context) {
bool subghz_protocol_decoder_protoview_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
SubGhzRadioPreset* preset)
{
UNUSED(context);
UNUSED(flipper_format);
UNUSED(preset);
@@ -73,13 +75,15 @@ bool subghz_protocol_decoder_protoview_serialize(
}
/* 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(flipper_format);
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_string_cat_printf(output, "Protoview");
}
@@ -112,4 +116,5 @@ const SubGhzProtocol* protoview_protocol_registry_items[] = {
const SubGhzProtocolRegistry protoview_protocol_registry = {
.items = protoview_protocol_registry_items,
.size = COUNT_OF(protoview_protocol_registry_items)};
.size = COUNT_OF(protoview_protocol_registry_items)
};
+19 -16
View File
@@ -9,9 +9,9 @@
#include "../app.h"
static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info) {
if(numbits < 30) return false;
const char* sync_patterns[3] = {
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) {
if (numbits < 30) return false;
const char *sync_patterns[3] = {
"10000000000000000000000000000001", /* 30 zero bits. */
"100000000000000000000000000000001", /* 31 zero bits. */
"1000000000000000000000000000000001", /* 32 zero bits. */
@@ -19,23 +19,26 @@ static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoView
uint32_t off;
int j;
for(j = 0; j < 3; j++) {
off = bitmap_seek_bits(bits, numbytes, 0, numbits, sync_patterns[j]);
if(off != BITMAP_SEEK_NOT_FOUND) break;
for (j = 0; j < 3; j++) {
off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_patterns[j]);
if (off != BITMAP_SEEK_NOT_FOUND) break;
}
if(off == BITMAP_SEEK_NOT_FOUND) return false;
if(DEBUG_MSG) FURI_LOG_E(TAG, "B4B1 preamble at: %lu", off);
off += strlen(sync_patterns[j]) - 1;
if (off == BITMAP_SEEK_NOT_FOUND) return false;
if (DEBUG_MSG) FURI_LOG_E(TAG, "B4B1 preamble at: %lu",off);
off += strlen(sync_patterns[j])-1;
uint8_t d[3]; /* 24 bits of data. */
uint32_t decoded = convert_from_line_code(d, sizeof(d), bits, numbytes, off, "1000", "1110");
uint32_t decoded =
convert_from_line_code(d,sizeof(d),bits,numbytes,off,"1000","1110");
if(DEBUG_MSG) FURI_LOG_E(TAG, "B4B1 decoded: %lu", decoded);
if(decoded != 24) return false;
snprintf(info->name, PROTOVIEW_MSG_STR_LEN, "PT/SC remote");
snprintf(info->raw, PROTOVIEW_MSG_STR_LEN, "%02X%02X%02X", d[0], d[1], d[2]);
info->len = off + (4 * 24);
if (DEBUG_MSG) FURI_LOG_E(TAG, "B4B1 decoded: %lu",decoded);
if (decoded != 24) return false;
snprintf(info->name,PROTOVIEW_MSG_STR_LEN,"PT/SC remote");
snprintf(info->raw,PROTOVIEW_MSG_STR_LEN,"%02X%02X%02X",d[0],d[1],d[2]);
info->len = off+(4*24);
return true;
}
ProtoViewDecoder B4B1Decoder = {"B4B1", decode};
ProtoViewDecoder B4B1Decoder = {
"B4B1", decode
};
@@ -1,72 +0,0 @@
/* Citroen TPMS. Usually 443.92 Mhz FSK.
*
* Preamble of ~14 high/low 52 us pulses
* Sync of high 100us pulse then 50us low
* Then Manchester bits, 10 bytes total.
* Simple XOR checksum. */
#include "../app.h"
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
* is more likely to happen if we don't pretend to receive from the
* very start of the message. */
uint32_t sync_len = 17;
const char* sync_pattern = "10101010101010110";
if(numbits - sync_len < 8 * 10) return false; /* Expect 10 bytes. */
uint64_t off = bitmap_seek_bits(bits, numbytes, 0, numbits, sync_pattern);
if(off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Renault TPMS preamble+sync found");
off += sync_len; /* Skip preamble + sync. */
uint8_t raw[10];
uint32_t decoded = convert_from_line_code(
raw, sizeof(raw), bits, numbytes, off, "01", "10"); /* Manchester. */
FURI_LOG_E(TAG, "Citroen TPMS decoded bits: %lu", decoded);
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
* is not included. The meaning of the first byte is unknown and
* we don't display it. */
uint8_t crc = 0;
for(int j = 1; j < 10; j++) crc ^= raw[j];
if(crc != 0) return false; /* Require sane checksum. */
int repeat = raw[5] & 0xf;
float kpa = (float)raw[6] * 1.364;
int temp = raw[7] - 50;
int battery = raw[8]; /* This may be the battery. It's not clear. */
snprintf(info->name, sizeof(info->name), "%s", "Citroen TPMS");
snprintf(
info->raw,
sizeof(info->raw),
"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],
raw[1],
raw[2],
raw[3],
raw[4],
raw[5],
raw[6],
raw[7],
raw[8],
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;
}
ProtoViewDecoder CitroenTPMSDecoder = {"Citroen TPMS", decode};
@@ -1,76 +0,0 @@
/* Ford tires TPMS. Usually 443.92 Mhz FSK (in Europe).
*
* 52 us short pules
* Preamble: 0101010101010101010101010101
* Sync: 0110 (that is 52 us gap + 104 us pulse + 52 us gap)
* Data: 8 bytes Manchester encoded
* 01 = zero
* 10 = one
*/
#include "../app.h"
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;
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");
off += sync_len; /* Skip preamble and sync. */
uint8_t raw[8];
uint32_t decoded = convert_from_line_code(
raw, sizeof(raw), bits, numbytes, off, "01", "10"); /* Manchester. */
FURI_LOG_E(TAG, "Ford TPMS decoded bits: %lu", decoded);
if(decoded < 8 * 8) return false; /* Require the full 8 bytes. */
/* CRC is just the sum of the first 7 bytes MOD 256. */
uint8_t crc = 0;
for(int j = 0; j < 7; j++) crc += raw[j];
if(crc != raw[7]) return false; /* Require sane CRC. */
float psi = 0.25 * (((raw[6] & 0x20) << 3) | raw[4]);
/* Temperature apperas to be valid only if the most significant
* bit of the value is not set. Otherwise its meaning is unknown.
* Likely useful to alternatively send temperature or other info. */
int temp = raw[5] & 0x80 ? 0 : raw[5] - 56;
int flags = raw[5] & 0x7f;
int car_moving = (raw[6] & 0x44) == 0x44;
snprintf(info->name, sizeof(info->name), "%s", "Ford TPMS");
snprintf(
info->raw,
sizeof(info->raw),
"%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],
raw[1],
raw[2],
raw[3],
raw[4],
raw[5],
raw[6],
raw[7]);
snprintf(
info->info1,
sizeof(info->info1),
"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
snprintf(info->info3, sizeof(info->info3), "Flags %d", flags);
snprintf(info->info4, sizeof(info->info4), "Moving %s", car_moving ? "yes" : "no");
return true;
}
ProtoViewDecoder FordTPMSDecoder = {"Ford TPMS", decode};
@@ -0,0 +1,87 @@
/* Microchip HCS200/HCS300/HSC301 KeeLoq, rolling code remotes.
*
* Usually 443.92 Mhz OOK, ~200us or ~400us pulse len, depending
* on the configuration.
*
* Preamble: 12 pairs of alternating pulse/gap.
* Sync: long gap of around 10 times the duration of the short-pulse.
* Data: pulse width encoded data. Each bit takes three cycles:
*
* 0 = 110
* 1 = 100
*
* There are a total of 66 bits transmitted.
* 0..31: 32 bits of encrypted rolling code.
* 32..59: Remote ID, 28 bits
* 60..63: Buttons pressed
* 64..64: Low battery if set
* 65..65: Always set to 1
*
* Bits in bytes are inverted: least significant bit is first.
* For some reason there is no checksum whatsoever, so we only decode
* if we find everything well formed.
*/
#include "../app.h"
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
* half the gap we expect (5 pulses times, one is the final zero in the
* 24 symbols high/low sequence, then other 4). */
const char *sync_pattern = "101010101010101010101010" "0000";
uint8_t sync_len = 24+4;
if (numbits-sync_len+sync_len < 3*66) return false;
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern);
if (off == BITMAP_SEEK_NOT_FOUND) return false;
off += sync_len;
/* Now there is half the gap left, but we allow from 3 to 7, instead of 5
* symbols of gap, to avoid missing the signal for a matter of wrong
* timing. */
uint8_t gap_len = 0;
while(gap_len <= 7 && bitmap_get(bits,numbytes,off+gap_len) == 0)
gap_len++;
if (gap_len < 3 || gap_len > 7) return false;
off += gap_len;
FURI_LOG_E(TAG, "Keeloq preamble+sync found");
uint8_t raw[9] = {0};
uint32_t decoded =
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
"110","100"); /* Pulse width modulation. */
FURI_LOG_E(TAG, "Keeloq decoded bits: %lu", decoded);
if (decoded < 66) return false; /* Require the full 66 bits. */
bitmap_reverse_bytes(raw,sizeof(raw)); /* Keeloq is LSB first. */
int buttons = raw[7]>>4;
int s3 = (buttons&1) != 0;
int s0 = (buttons&2) != 0;
int s1 = (buttons&4) != 0;
int s2 = (buttons&8) != 0;
int remote_id = ((raw[7]&0x0f) << 24) |
(raw[6] << 16) |
(raw[5] << 8) |
(raw[4] << 0);
int lowbat = raw[8]&0x80;
snprintf(info->name,sizeof(info->name),"%s","Keeloq remote");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7],raw[8]);
snprintf(info->info1,sizeof(info->info1),"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;
}
ProtoViewDecoder KeeloqDecoder = {
"Keeloq", decode
};
@@ -6,84 +6,60 @@
#include "../app.h"
static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info) {
if(numbits < 32) return false;
const char* sync_pattern = "01100110"
"01100110"
"10010110"
"10010110";
uint64_t off = bitmap_seek_bits(bits, numbytes, 0, numbits, sync_pattern);
if(off == BITMAP_SEEK_NOT_FOUND) return false;
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) {
if (numbits < 32) return false;
const char *sync_pattern = "01100110" "01100110" "10010110" "10010110";
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern);
if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Oregon2 preamble+sync found");
off += 32; /* Skip preamble. */
uint8_t buffer[8], raw[8] = {0};
uint32_t decoded =
convert_from_line_code(buffer, sizeof(buffer), bits, numbytes, off, "1001", "0110");
convert_from_line_code(buffer,sizeof(buffer),bits,numbytes,off,"1001","0110");
FURI_LOG_E(TAG, "Oregon2 decoded bits: %lu", decoded);
if(decoded < 11 * 4) return false; /* Minimum len to extract some data. */
if (decoded < 11*4) return false; /* Minimum len to extract some data. */
char temp[3] = {0}, deviceid[2] = {0}, hum[2] = {0};
for(int j = 0; j < 64; j += 4) {
for (int j = 0; j < 64; j += 4) {
uint8_t nib[1];
nib[0] =
(bitmap_get(buffer, 8, j + 0) | bitmap_get(buffer, 8, j + 1) << 1 |
bitmap_get(buffer, 8, j + 2) << 2 | bitmap_get(buffer, 8, j + 3) << 3);
if(DEBUG_MSG) FURI_LOG_E(TAG, "Not inverted nibble[%d]: %x", j / 4, (unsigned int)nib[0]);
raw[j / 8] |= nib[0] << (4 - (j % 4));
switch(j / 4) {
case 1:
deviceid[0] |= nib[0];
break;
case 0:
deviceid[0] |= nib[0] << 4;
break;
case 3:
deviceid[1] |= nib[0];
break;
case 2:
deviceid[1] |= nib[0] << 4;
break;
case 10:
temp[0] = nib[0];
break;
nib[0] = (bitmap_get(buffer,8,j+0) |
bitmap_get(buffer,8,j+1) << 1 |
bitmap_get(buffer,8,j+2) << 2 |
bitmap_get(buffer,8,j+3) << 3);
if (DEBUG_MSG) FURI_LOG_E(TAG, "Not inverted nibble[%d]: %x", j/4, (unsigned int)nib[0]);
raw[j/8] |= nib[0] << (4-(j%4));
switch(j/4) {
case 1: deviceid[0] |= nib[0]; break;
case 0: deviceid[0] |= nib[0] << 4; break;
case 3: deviceid[1] |= nib[0]; break;
case 2: deviceid[1] |= nib[0] << 4; break;
case 10: temp[0] = nib[0]; break;
/* Fixme: take the temperature sign from nibble 11. */
case 9:
temp[1] = nib[0];
break;
case 8:
temp[2] = nib[0];
break;
case 13:
hum[0] = nib[0];
break;
case 12:
hum[1] = nib[0];
break;
case 9: temp[1] = nib[0]; break;
case 8: temp[2] = nib[0]; break;
case 13: hum[0] = nib[0]; break;
case 12: hum[1] = nib[0]; break;
}
}
snprintf(info->name, sizeof(info->name), "%s", "Oregon v2.1");
snprintf(info->name,sizeof(info->name),"%s","Oregon v2.1");
/* The following line crashes the Flipper because of broken
* snprintf() implementation. */
snprintf(
info->raw,
sizeof(info->raw),
"%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],
raw[1],
raw[2],
raw[3],
raw[4],
raw[5],
raw[6],
raw[7]);
snprintf(info->info1, sizeof(info->info1), "Sensor ID %02X%02X", deviceid[0], deviceid[1]);
snprintf(info->info2, sizeof(info->info2), "Temperature %d%d.%d", temp[0], temp[1], temp[2]);
snprintf(info->info3, sizeof(info->info3), "Humidity %d%d", hum[0], hum[1]);
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7]);
snprintf(info->info1,sizeof(info->info1),"Sensor ID %02X%02X",
deviceid[0], deviceid[1]);
snprintf(info->info2,sizeof(info->info2),"Temperature %d%d.%d",
temp[0],temp[1],temp[2]);
snprintf(info->info3,sizeof(info->info3),"Humidity %d%d",
hum[0],hum[1]);
return true;
}
ProtoViewDecoder Oregon2Decoder = {"Oregon2", decode};
ProtoViewDecoder Oregon2Decoder = {
"Oregon2", decode
};
@@ -1,6 +0,0 @@
11001100110011001100110011001100110011001100110011001100110 (Preamble)
10 01 01 10 10 01 01 10 (Sync)
01 10 10 01 10 01 10 01 01 10 10 01 01 10 01 10 10 01 01 10 10 01 10 01 10 01 10 01 10 01 10 01 01 10 10 01 10 01 10 01 01 10 01 10 01 10 01 10 01 10 01 10 10 01 01 10 01 10 10 01 10 01 10 01 10 01 10 01 01 10 10 01 10 01 01 10 01 10 10 01 01 10 10 01 10 01 10 01 10 01 10 01 10 01 11 0
We need to seek the following bytes: 01100110 01100110 10010110 10010110
0x66 0x66 96 96
@@ -1,71 +0,0 @@
/* Renault tires TPMS. Usually 443.92 Mhz FSK.
*
* Preamble + sync + Manchester bits. ~48us short pulse.
* 9 Bytes in total not counting the preamble. */
#include "../app.h"
#define USE_TEST_VECTOR 0
static const char* test_vector =
"...01010101010101010110" // Preamble + sync
/* The following is Marshal encoded, so each two characters are
* actaully one bit. 01 = 0, 10 = 1. */
"010110010110" // Flags.
"10011001101010011001" // Pressure, multiply by 0.75 to obtain kpa.
// 244 kpa here.
"1010010110011010" // Temperature, subtract 30 to obtain celsius. 22C here.
"1001010101101001"
"0101100110010101"
"1001010101100110" // Tire ID. 0x7AD779 here.
"0101010101010101"
"0101010101010101" // Two FF bytes (usually). Unknown.
"0110010101010101"; // CRC8 with (poly 7, initialization 0).
static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info) {
if(USE_TEST_VECTOR) { /* Test vector to check that decoding works. */
bitmap_set_pattern(bits, numbytes, test_vector);
numbits = strlen(test_vector);
}
if(numbits - 12 < 9 * 8) return false;
const char* sync_pattern = "01010101010101010110";
uint64_t off = bitmap_seek_bits(bits, numbytes, 0, numbits, sync_pattern);
if(off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Renault TPMS preamble+sync found");
off += 20; /* Skip preamble. */
uint8_t raw[9];
uint32_t decoded = convert_from_line_code(
raw, sizeof(raw), bits, numbytes, off, "01", "10"); /* Manchester. */
FURI_LOG_E(TAG, "Renault TPMS decoded bits: %lu", decoded);
if(decoded < 8 * 9) return false; /* Require the full 9 bytes. */
if(crc8(raw, 8, 0, 7) != raw[8]) return false; /* Require sane CRC. */
float kpa = 0.75 * ((uint32_t)((raw[0] & 3) << 8) | raw[1]);
int temp = raw[2] - 30;
snprintf(info->name, sizeof(info->name), "%s", "Renault TPMS");
snprintf(
info->raw,
sizeof(info->raw),
"%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],
raw[1],
raw[2],
raw[3],
raw[4],
raw[5],
raw[6],
raw[7],
raw[8]);
snprintf(info->info1, sizeof(info->info1), "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;
}
ProtoViewDecoder RenaultTPMSDecoder = {"Renault TPMS", decode};
@@ -1,78 +0,0 @@
/* Schrader TPMS. Usually 443.92 Mhz OOK, 120us pulse len.
*
* 500us high pulse + Preamble + Manchester coded bits where:
* 1 = 10
* 0 = 01
*
* 60 bits of data total (first 4 nibbles is the preamble, 0xF).
*
* Used in FIAT-Chrysler, Mercedes, ... */
#include "../app.h"
#define USE_TEST_VECTOR 0
static const char* test_vector =
"000000111101010101011010010110010110101001010110100110011001100101010101011010100110100110011010101010101010101010101010101010101010101010101010";
static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info) {
if(USE_TEST_VECTOR) { /* Test vector to check that decoding works. */
bitmap_set_pattern(bits, numbytes, test_vector);
numbits = strlen(test_vector);
}
if(numbits < 64) return false; /* Preamble + data. */
const char* sync_pattern = "1111010101"
"01011010";
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");
off += 10; /* Skip just the long pulse and the first 3 bits of sync, so
that we have the first byte of data with the sync nibble
0011 = 0x3. */
uint8_t raw[8];
uint32_t decoded = convert_from_line_code(
raw, sizeof(raw), bits, numbytes, off, "01", "10"); /* Manchester code. */
FURI_LOG_E(TAG, "Schrader TPMS decoded bits: %lu", decoded);
if(decoded < 64) return false; /* Require the full 8 bytes. */
raw[0] |= 0xf0; // Fix the preamble nibble for checksum computation.
uint8_t cksum = crc8(raw, sizeof(raw) - 1, 0xf0, 0x7);
if(cksum != raw[7]) {
FURI_LOG_E(TAG, "Schrader TPMS checksum mismatch");
return false;
}
float kpa = (float)raw[5] * 2.5;
int temp = raw[6] - 50;
snprintf(info->name, sizeof(info->name), "%s", "Schrader TPMS");
snprintf(
info->raw,
sizeof(info->raw),
"%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],
raw[1],
raw[2],
raw[3],
raw[4],
raw[5],
raw[6],
raw[7]);
snprintf(
info->info1,
sizeof(info->info1),
"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;
}
ProtoViewDecoder SchraderTPMSDecoder = {"Schrader TPMS", decode};
@@ -1,84 +0,0 @@
/* Toyota tires TPMS. Usually 443.92 Mhz FSK (In Europe).
*
* Preamble + sync + 64 bits of data. ~48us short pulse length.
*
* The preamble + sync is something like:
*
* 10101010101 (preamble) + 001111[1] (sync)
*
* Note: the final [1] means that sometimes it is four 1s, sometimes
* five, depending on the short pulse length detection and the exact
* duration of the high long pulse. After the sync, a differential
* Manchester encoded payload follows. However the Flipper's CC1101
* often can't decode correctly the initial alternating pattern 101010101,
* so what we do is to seek just the sync, that is "001111" or "0011111",
* however we now that it must be followed by one differenitally encoded
* bit, so we can use also the first bit of data to force a more robust
* detection, and look for one of the following:
*
* [001111]00
* [0011111]00
* [001111]01
* [0011111]01
*/
#include "../app.h"
static bool decode(uint8_t* bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo* info) {
if(numbits - 6 < 64 * 2)
return false; /* Ask for 64 bit of data (each bit
is two symbols in the bitmap). */
char* sync[] = {"00111100", "001111100", "00111101", "001111101", NULL};
int j;
uint32_t off = 0;
for(j = 0; sync[j]; j++) {
off = bitmap_seek_bits(bits, numbytes, 0, numbits, sync[j]);
if(off != BITMAP_SEEK_NOT_FOUND) {
off += strlen(sync[j]) - 2;
break;
}
}
if(off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Toyota TPMS sync[%s] found", sync[j]);
uint8_t raw[9];
uint32_t decoded = convert_from_diff_manchester(raw, sizeof(raw), bits, numbytes, off, true);
FURI_LOG_E(TAG, "Toyota TPMS decoded bits: %lu", decoded);
if(decoded < 8 * 9) return false; /* Require the full 8 bytes. */
if(crc8(raw, 8, 0x80, 7) != raw[8]) return false; /* Require sane CRC. */
float kpa = (float)((raw[4] & 0x7f) << 1 | raw[5] >> 7) * 0.25 - 7;
int temp = ((raw[5] & 0x7f) << 1 | raw[6] >> 7) - 40;
snprintf(info->name, sizeof(info->name), "%s", "Toyota TPMS");
snprintf(
info->raw,
sizeof(info->raw),
"%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],
raw[1],
raw[2],
raw[3],
raw[4],
raw[5],
raw[6],
raw[7],
raw[8]);
snprintf(
info->info1,
sizeof(info->info1),
"Tire ID %02X%02X%02X%02X",
raw[0],
raw[1],
raw[2],
raw[3]);
snprintf(info->info1, sizeof(info->info1), "Pressure %.2f psi", (double)kpa);
snprintf(info->info2, sizeof(info->info2), "Temperature %d C", temp);
return true;
}
ProtoViewDecoder ToyotaTPMSDecoder = {"Toyota TPMS", decode};
@@ -0,0 +1,60 @@
/* Citroen TPMS. Usually 443.92 Mhz FSK.
*
* Preamble of ~14 high/low 52 us pulses
* Sync of high 100us pulse then 50us low
* Then Manchester bits, 10 bytes total.
* Simple XOR checksum. */
#include "../../app.h"
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
* is more likely to happen if we don't pretend to receive from the
* very start of the message. */
uint32_t sync_len = 17;
const char *sync_pattern = "10101010101010110";
if (numbits-sync_len < 8*10) return false; /* Expect 10 bytes. */
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern);
if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Renault TPMS preamble+sync found");
off += sync_len; /* Skip preamble + sync. */
uint8_t raw[10];
uint32_t decoded =
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
"01","10"); /* Manchester. */
FURI_LOG_E(TAG, "Citroen TPMS decoded bits: %lu", decoded);
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
* is not included. The meaning of the first byte is unknown and
* we don't display it. */
uint8_t crc = 0;
for (int j = 1; j < 10; j++) crc ^= raw[j];
if (crc != 0) return false; /* Require sane checksum. */
int repeat = raw[5] & 0xf;
float kpa = (float)raw[6]*1.364;
int temp = raw[7]-50;
int battery = raw[8]; /* This may be the battery. It's not clear. */
snprintf(info->name,sizeof(info->name),"%s","Citroen TPMS");
snprintf(info->raw,sizeof(info->raw),
"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7],raw[8],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;
}
ProtoViewDecoder CitroenTPMSDecoder = {
"Citroen TPMS", decode
};
@@ -0,0 +1,64 @@
/* Ford tires TPMS. Usually 443.92 Mhz FSK (in Europe).
*
* 52 us short pules
* Preamble: 0101010101010101010101010101
* Sync: 0110 (that is 52 us gap + 104 us pulse + 52 us gap)
* Data: 8 bytes Manchester encoded
* 01 = zero
* 10 = one
*/
#include "../../app.h"
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;
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");
off += sync_len; /* Skip preamble and sync. */
uint8_t raw[8];
uint32_t decoded =
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
"01","10"); /* Manchester. */
FURI_LOG_E(TAG, "Ford TPMS decoded bits: %lu", decoded);
if (decoded < 8*8) return false; /* Require the full 8 bytes. */
/* CRC is just the sum of the first 7 bytes MOD 256. */
uint8_t crc = 0;
for (int j = 0; j < 7; j++) crc += raw[j];
if (crc != raw[7]) return false; /* Require sane CRC. */
float psi = 0.25 * (((raw[6]&0x20)<<3)|raw[4]);
/* Temperature apperas to be valid only if the most significant
* bit of the value is not set. Otherwise its meaning is unknown.
* Likely useful to alternatively send temperature or other info. */
int temp = raw[5] & 0x80 ? 0 : raw[5]-56;
int flags = raw[5] & 0x7f;
int car_moving = (raw[6] & 0x44) == 0x44;
snprintf(info->name,sizeof(info->name),"%s","Ford TPMS");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7]);
snprintf(info->info1,sizeof(info->info1),"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
snprintf(info->info3,sizeof(info->info3),"Flags %d", flags);
snprintf(info->info4,sizeof(info->info4),"Moving %s", car_moving ? "yes" : "no");
return true;
}
ProtoViewDecoder FordTPMSDecoder = {
"Ford TPMS", decode
};
@@ -0,0 +1,66 @@
/* Renault tires TPMS. Usually 443.92 Mhz FSK.
*
* Preamble + sync + Manchester bits. ~48us short pulse.
* 9 Bytes in total not counting the preamble. */
#include "../../app.h"
#define USE_TEST_VECTOR 0
static const char *test_vector =
"...01010101010101010110" // Preamble + sync
/* The following is Marshal encoded, so each two characters are
* actaully one bit. 01 = 0, 10 = 1. */
"010110010110" // Flags.
"10011001101010011001" // Pressure, multiply by 0.75 to obtain kpa.
// 244 kpa here.
"1010010110011010" // Temperature, subtract 30 to obtain celsius. 22C here.
"1001010101101001"
"0101100110010101"
"1001010101100110" // Tire ID. 0x7AD779 here.
"0101010101010101"
"0101010101010101" // Two FF bytes (usually). Unknown.
"0110010101010101"; // CRC8 with (poly 7, initialization 0).
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) {
if (USE_TEST_VECTOR) { /* Test vector to check that decoding works. */
bitmap_set_pattern(bits,numbytes,test_vector);
numbits = strlen(test_vector);
}
if (numbits-12 < 9*8) return false;
const char *sync_pattern = "01010101010101010110";
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern);
if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Renault TPMS preamble+sync found");
off += 20; /* Skip preamble. */
uint8_t raw[9];
uint32_t decoded =
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
"01","10"); /* Manchester. */
FURI_LOG_E(TAG, "Renault TPMS decoded bits: %lu", decoded);
if (decoded < 8*9) return false; /* Require the full 9 bytes. */
if (crc8(raw,8,0,7) != raw[8]) return false; /* Require sane CRC. */
float kpa = 0.75 *((uint32_t)((raw[0]&3)<<8) | raw[1]);
int temp = raw[2]-30;
snprintf(info->name,sizeof(info->name),"%s","Renault TPMS");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7],raw[8]);
snprintf(info->info1,sizeof(info->info1),"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;
}
ProtoViewDecoder RenaultTPMSDecoder = {
"Renault TPMS", decode
};
@@ -0,0 +1,65 @@
/* Schrader TPMS. Usually 443.92 Mhz OOK, 120us pulse len.
*
* 500us high pulse + Preamble + Manchester coded bits where:
* 1 = 10
* 0 = 01
*
* 60 bits of data total (first 4 nibbles is the preamble, 0xF).
*
* Used in FIAT-Chrysler, Mercedes, ... */
#include "../../app.h"
#define USE_TEST_VECTOR 0
static const char *test_vector = "000000111101010101011010010110010110101001010110100110011001100101010101011010100110100110011010101010101010101010101010101010101010101010101010";
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) {
if (USE_TEST_VECTOR) { /* Test vector to check that decoding works. */
bitmap_set_pattern(bits,numbytes,test_vector);
numbits = strlen(test_vector);
}
if (numbits < 64) return false; /* Preamble + data. */
const char *sync_pattern = "1111010101" "01011010";
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");
off += 10; /* Skip just the long pulse and the first 3 bits of sync, so
that we have the first byte of data with the sync nibble
0011 = 0x3. */
uint8_t raw[8];
uint32_t decoded =
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
"01","10"); /* Manchester code. */
FURI_LOG_E(TAG, "Schrader TPMS decoded bits: %lu", decoded);
if (decoded < 64) return false; /* Require the full 8 bytes. */
raw[0] |= 0xf0; // Fix the preamble nibble for checksum computation.
uint8_t cksum = crc8(raw,sizeof(raw)-1,0xf0,0x7);
if (cksum != raw[7]) {
FURI_LOG_E(TAG, "Schrader TPMS checksum mismatch");
return false;
}
float kpa = (float)raw[5]*2.5;
int temp = raw[6]-50;
snprintf(info->name,sizeof(info->name),"%s","Schrader TPMS");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7]);
snprintf(info->info1,sizeof(info->info1),"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;
}
ProtoViewDecoder SchraderTPMSDecoder = {
"Schrader TPMS", decode
};
@@ -0,0 +1,63 @@
/* Schrader variant EG53MA4 TPMS.
* Usually 443.92 Mhz OOK, 100us pulse len.
*
* Preamble: alternating pulse/gap, 100us.
* Sync (as pulses and gaps): "01100101", already part of the data stream
* (first nibble) corresponding to 0x4
*
* A total of 10 bytes payload, Manchester encoded.
*
* 0 = 01
* 1 = 10
*
* Used in certain Open cars and others.
*/
#include "../../app.h"
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;
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");
off += sync_len-8; /* Skip preamble, not sync that is part of the data. */
uint8_t raw[10];
uint32_t decoded =
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
"01","10"); /* Manchester code. */
FURI_LOG_E(TAG, "Schrader EG53MA4 TPMS decoded bits: %lu", decoded);
if (decoded < 10*8) return false; /* Require the full 10 bytes. */
/* CRC is just all bytes added mod 256. */
uint8_t crc = 0;
for (int j = 0; j < 9; j++) crc += raw[j];
if (crc != raw[9]) return false; /* Require sane CRC. */
/* To convert the raw pressure to kPa, RTL433 uses 2.5, but is likely
* wrong. Searching on Google for users experimenting with the value
* reported, the value appears to be 2.75. */
float kpa = (float)raw[7]*2.75;
int temp_f = raw[8];
int temp_c = (temp_f-32)*5/9; /* Convert Fahrenheit to Celsius. */
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",
raw[0],raw[1],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;
}
ProtoViewDecoder SchraderEG53MA4TPMSDecoder = {
"Schrader EG53MA4 TPMS", decode
};
@@ -0,0 +1,77 @@
/* Toyota tires TPMS. Usually 443.92 Mhz FSK (In Europe).
*
* Preamble + sync + 64 bits of data. ~48us short pulse length.
*
* The preamble + sync is something like:
*
* 10101010101 (preamble) + 001111[1] (sync)
*
* Note: the final [1] means that sometimes it is four 1s, sometimes
* five, depending on the short pulse length detection and the exact
* duration of the high long pulse. After the sync, a differential
* Manchester encoded payload follows. However the Flipper's CC1101
* often can't decode correctly the initial alternating pattern 101010101,
* so what we do is to seek just the sync, that is "001111" or "0011111",
* however we now that it must be followed by one differenitally encoded
* bit, so we can use also the first symbol of data to force a more robust
* detection, and look for one of the following:
*
* [001111]00
* [0011111]00
* [001111]01
* [0011111]01
*/
#include "../../app.h"
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) {
if (numbits-6 < 64*2) return false; /* Ask for 64 bit of data (each bit
is two symbols in the bitmap). */
char *sync[] = {
"00111100",
"001111100",
"00111101",
"001111101",
NULL
};
int j;
uint32_t off = 0;
for (j = 0; sync[j]; j++) {
off = bitmap_seek_bits(bits,numbytes,0,numbits,sync[j]);
if (off != BITMAP_SEEK_NOT_FOUND) {
off += strlen(sync[j])-2;
break;
}
}
if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Toyota TPMS sync[%s] found", sync[j]);
uint8_t raw[9];
uint32_t decoded =
convert_from_diff_manchester(raw,sizeof(raw),bits,numbytes,off,true);
FURI_LOG_E(TAG, "Toyota TPMS decoded bits: %lu", decoded);
if (decoded < 8*9) return false; /* Require the full 8 bytes. */
if (crc8(raw,8,0x80,7) != raw[8]) return false; /* Require sane CRC. */
float kpa = (float)((raw[4]&0x7f)<<1 | raw[5]>>7) * 0.25 - 7;
int temp = ((raw[5]&0x7f)<<1 | raw[6]>>7) - 40;
snprintf(info->name,sizeof(info->name),"%s","Toyota TPMS");
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7],raw[8]);
snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X%02X",
raw[0],raw[1],raw[2],raw[3]);
snprintf(info->info1,sizeof(info->info1),"Pressure %.2f psi", (double)kpa);
snprintf(info->info2,sizeof(info->info2),"Temperature %d C", temp);
return true;
}
ProtoViewDecoder ToyotaTPMSDecoder = {
"Toyota TPMS", decode
};
+137 -161
View File
@@ -3,8 +3,8 @@
#include "app.h"
bool decode_signal(RawSamplesBuffer* s, uint64_t len, ProtoViewMsgInfo* info);
void initialize_msg_info(ProtoViewMsgInfo* i);
bool decode_signal(RawSamplesBuffer *s, uint64_t len, ProtoViewMsgInfo *info);
void initialize_msg_info(ProtoViewMsgInfo *i);
/* =============================================================================
* Raw signal detection
@@ -17,7 +17,7 @@ uint32_t duration_delta(uint32_t a, uint32_t b) {
}
/* 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_offset = 0;
app->signal_decoded = false;
@@ -38,47 +38,47 @@ void reset_current_signal(ProtoViewApp* app) {
* For instance Oregon2 sensors, in the case of protocol 2.1 will send
* pulses of ~400us (RF on) VS ~580us (RF off). */
#define SEARCH_CLASSES 3
uint32_t search_coherent_signal(RawSamplesBuffer* s, uint32_t idx) {
uint32_t search_coherent_signal(RawSamplesBuffer *s, uint32_t idx) {
struct {
uint32_t dur[2]; /* dur[0] = low, dur[1] = high */
uint32_t count[2]; /* Associated observed frequency. */
uint32_t dur[2]; /* dur[0] = low, dur[1] = high */
uint32_t count[2]; /* Associated observed frequency. */
} classes[SEARCH_CLASSES];
memset(classes, 0, sizeof(classes));
memset(classes,0,sizeof(classes));
uint32_t minlen = 30, maxlen = 4000; /* Depends on data rate, here we
allow for high and low. */
uint32_t len = 0; /* Observed len of coherent samples. */
s->short_pulse_dur = 0;
for(uint32_t j = idx; j < idx + 500; j++) {
for (uint32_t j = idx; j < idx+500; j++) {
bool level;
uint32_t dur;
raw_samples_get(s, j, &level, &dur);
if(dur < minlen || dur > maxlen) break; /* return. */
if (dur < minlen || dur > maxlen) break; /* return. */
/* Let's see if it matches a class we already have or if we
* can populate a new (yet empty) class. */
uint32_t k;
for(k = 0; k < SEARCH_CLASSES; k++) {
if(classes[k].count[level] == 0) {
for (k = 0; k < SEARCH_CLASSES; k++) {
if (classes[k].count[level] == 0) {
classes[k].dur[level] = dur;
classes[k].count[level] = 1;
break; /* Sample accepted. */
} else {
uint32_t classavg = classes[k].dur[level];
uint32_t count = classes[k].count[level];
uint32_t delta = duration_delta(dur, classavg);
uint32_t delta = duration_delta(dur,classavg);
/* Is the difference in duration between this signal and
* the class we are inspecting less than a given percentage?
* If so, accept this signal. */
if(delta < classavg / 5) { /* 100%/5 = 20%. */
if (delta < classavg/5) { /* 100%/5 = 20%. */
/* It is useful to compute the average of the class
* we are observing. We know how many samples we got so
* far, so we can recompute the average easily.
* By always having a better estimate of the pulse len
* we can avoid missing next samples in case the first
* observed samples are too off. */
classavg = ((classavg * count) + dur) / (count + 1);
classavg = ((classavg * count) + dur) / (count+1);
classes[k].dur[level] = classavg;
classes[k].count[level]++;
break; /* Sample accepted. */
@@ -86,7 +86,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
* one. */
@@ -96,12 +96,14 @@ uint32_t search_coherent_signal(RawSamplesBuffer* s, uint32_t idx) {
/* Update the buffer setting the shortest pulse we found
* among the three classes. This will be used when scaling
* for visualization. */
uint32_t short_dur[2] = {0, 0};
for(int j = 0; j < SEARCH_CLASSES; j++) {
for(int level = 0; level < 2; level++) {
if(classes[j].dur[level] == 0) continue;
if(classes[j].count[level] < 3) continue;
if(short_dur[level] == 0 || short_dur[level] > classes[j].dur[level]) {
uint32_t short_dur[2] = {0,0};
for (int j = 0; j < SEARCH_CLASSES; j++) {
for (int level = 0; level < 2; level++) {
if (classes[j].dur[level] == 0) continue;
if (classes[j].count[level] < 3) continue;
if (short_dur[level] == 0 ||
short_dur[level] > classes[j].dur[level])
{
short_dur[level] = classes[j].dur[level];
}
}
@@ -110,9 +112,9 @@ uint32_t search_coherent_signal(RawSamplesBuffer* s, uint32_t idx) {
/* Use the average between high and low short pulses duration.
* Often they are a bit different, and using the average is more robust
* when we do decoding sampling at short_pulse_dur intervals. */
if(short_dur[0] == 0) short_dur[0] = short_dur[1];
if(short_dur[1] == 0) short_dur[1] = short_dur[0];
s->short_pulse_dur = (short_dur[0] + short_dur[1]) / 2;
if (short_dur[0] == 0) short_dur[0] = short_dur[1];
if (short_dur[1] == 0) short_dur[1] = short_dur[0];
s->short_pulse_dur = (short_dur[0]+short_dur[1])/2;
return len;
}
@@ -121,56 +123,54 @@ 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
* be just noise is found, it is set in DetectedSamples global signal
* buffer, that is what is rendered on the screen. */
void scan_for_signal(ProtoViewApp* app) {
void scan_for_signal(ProtoViewApp *app) {
/* We need to work on a copy: the RawSamples buffer is populated
* by the background thread receiving data. */
RawSamplesBuffer* copy = raw_samples_alloc();
raw_samples_copy(copy, RawSamples);
RawSamplesBuffer *copy = raw_samples_alloc();
raw_samples_copy(copy,RawSamples);
/* Try to seek on data that looks to have a regular high low high low
* pattern. */
uint32_t minlen = 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
mistake noise for signal. */
ProtoViewMsgInfo* info = malloc(sizeof(ProtoViewMsgInfo));
ProtoViewMsgInfo *info = malloc(sizeof(ProtoViewMsgInfo));
uint32_t i = 0;
while(i < copy->total - 1) {
uint32_t thislen = search_coherent_signal(copy, i);
while (i < copy->total-1) {
uint32_t thislen = search_coherent_signal(copy,i);
/* For messages that are long enough, attempt decoding. */
if(thislen > minlen) {
if (thislen > minlen) {
initialize_msg_info(info);
uint32_t saved_idx = copy->idx; /* Save index, see later. */
/* decode_signal() expects the detected signal to start
* from index .*/
raw_samples_center(copy, i);
bool decoded = decode_signal(copy, thislen, info);
raw_samples_center(copy,i);
bool decoded = decode_signal(copy,thislen,info);
copy->idx = saved_idx; /* Restore the index as we are scanning
the signal in the loop. */
/* Accept this signal as the new signal if either it's longer
* than the previous undecoded one, or the previous one was
* unknown and this is decoded. */
if((thislen > app->signal_bestlen && app->signal_decoded == false) ||
(app->signal_decoded == false && decoded)) {
if ((thislen > app->signal_bestlen && app->signal_decoded == false)
|| (app->signal_decoded == false && decoded))
{
app->signal_info = *info;
app->signal_bestlen = thislen;
app->signal_decoded = decoded;
raw_samples_copy(DetectedSamples, copy);
raw_samples_center(DetectedSamples, i);
FURI_LOG_E(
TAG,
"===> Displayed sample updated (%d samples %lu us)",
(int)thislen,
DetectedSamples->short_pulse_dur);
raw_samples_copy(DetectedSamples,copy);
raw_samples_center(DetectedSamples,i);
FURI_LOG_E(TAG, "===> Displayed sample updated (%d samples %lu us)",
(int)thislen, DetectedSamples->short_pulse_dur);
/* Adjust raw view scale if the signal has an high
* data rate. */
if(DetectedSamples->short_pulse_dur < 75)
if (DetectedSamples->short_pulse_dur < 75)
app->us_scale = 10;
else if(DetectedSamples->short_pulse_dur < 145)
else if (DetectedSamples->short_pulse_dur < 145)
app->us_scale = 30;
}
}
@@ -196,43 +196,48 @@ void scan_for_signal(ProtoViewApp* app) {
/* Set the 'bitpos' bit to value 'val', in the specified bitmap
* 'b' of len 'blen'.
* Out of range bits will silently be discarded. */
void bitmap_set(uint8_t* b, uint32_t blen, uint32_t bitpos, bool val) {
uint32_t byte = bitpos / 8;
uint32_t bit = 7 - (bitpos & 7);
if(byte >= blen) return;
if(val)
b[byte] |= 1 << bit;
void bitmap_set(uint8_t *b, uint32_t blen, uint32_t bitpos, bool val) {
uint32_t byte = bitpos/8;
uint32_t bit = 7-(bitpos&7);
if (byte >= blen) return;
if (val)
b[byte] |= 1<<bit;
else
b[byte] &= ~(1 << bit);
b[byte] &= ~(1<<bit);
}
/* Get the bit 'bitpos' of the bitmap 'b' of 'blen' bytes.
* Out of range bits return false (not bit set). */
bool bitmap_get(uint8_t* b, uint32_t blen, uint32_t bitpos) {
uint32_t byte = bitpos / 8;
uint32_t bit = 7 - (bitpos & 7);
if(byte >= blen) return 0;
return (b[byte] & (1 << bit)) != 0;
bool bitmap_get(uint8_t *b, uint32_t blen, uint32_t bitpos) {
uint32_t byte = bitpos/8;
uint32_t bit = 7-(bitpos&7);
if (byte >= blen) return 0;
return (b[byte] & (1<<bit)) != 0;
}
/* We decode bits assuming the first bit we receive is the LSB
* (see bitmap_set/get functions). Many devices send data
/* We decode bits assuming the first bit we receive is the MSB
* (see bitmap_set/get functions). Certain devices send data
* encoded in the reverse way. */
void bitmap_invert_bytes_bits(uint8_t* p, uint32_t len) {
for(uint32_t j = 0; j < len * 8; j += 8) {
bool bits[8];
for(int i = 0; i < 8; i++) bits[i] = bitmap_get(p, len, j + i);
for(int i = 0; i < 8; i++) bitmap_set(p, len, j + i, bits[7 - i]);
void bitmap_reverse_bytes(uint8_t *p, uint32_t len) {
for (uint32_t j = 0; j < len; j++) {
uint32_t b = p[j];
/* Step 1: swap the two nibbles: 12345678 -> 56781234 */
b = (b&0xf0)>>4 | (b&0x0f)<<4;
/* Step 2: swap adjacent pairs : 56781234 -> 78563412 */
b = (b&0xcc)>>2 | (b&0x33)<<2;
/* Step 3: swap adjacent bits : 78563412 -> 87654321 */
b = (b&0xaa)>>1 | (b&0x55)<<1;
p[j] = b;
}
}
/* 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'
* position. */
bool bitmap_match_bits(uint8_t* b, uint32_t blen, uint32_t bitpos, const char* bits) {
for(size_t j = 0; bits[j]; j++) {
bool bitmap_match_bits(uint8_t *b, uint32_t blen, uint32_t bitpos, const char *bits) {
for (size_t j = 0; bits[j]; j++) {
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;
}
@@ -245,17 +250,12 @@ 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
* for the kind of patterns we search we'll have a lot of early stops so
* 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 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;
uint32_t bitmap_seek_bits(uint8_t *b, uint32_t blen, uint32_t startpos, uint32_t maxbits, 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;
}
@@ -264,10 +264,10 @@ uint32_t bitmap_seek_bits(
* 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
* to actually receive a given signal. */
void bitmap_set_pattern(uint8_t* b, uint32_t blen, const char* pat) {
void bitmap_set_pattern(uint8_t *b, uint32_t blen, const char *pat) {
uint32_t i = 0;
while(pat[i]) {
bitmap_set(b, blen, i, pat[i] == '1');
bitmap_set(b,blen,i,pat[i] == '1');
i++;
}
}
@@ -299,36 +299,31 @@ void bitmap_set_pattern(uint8_t* b, uint32_t blen, const char* pat) {
* bits set into the buffer 'b'. The 'rate' argument, in microseconds, is
* the detected short-pulse duration. We expect the line code to be
* 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) {
if(rate == 0) return 0; /* We can't perform the conversion. */
uint32_t convert_signal_to_bits(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;
for(uint32_t j = 0; j < count; j++) {
for (uint32_t j = 0; j < count; j++) {
uint32_t dur;
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 rest = dur % rate; /* How much we are left with. */
if(rest > rate / 2) numbits++; /* There is another one. */
uint32_t rest = dur % rate; /* How much we are left with. */
if (rest > rate/2) numbits++; /* There is another one. */
/* Limit how much a single sample can spawn. There are likely no
* protocols doing such long pulses when the rate is low. */
if(numbits > 1024) numbits = 1024;
if (numbits > 1024) numbits = 1024;
if(0) /* Super verbose, so not under the DEBUG_MSG define. */
FURI_LOG_E(TAG, "%lu converted into %lu (%d) bits", dur, numbits, (int)level);
if (0) /* Super verbose, so not under the DEBUG_MSG define. */
FURI_LOG_E(TAG, "%lu converted into %lu (%d) bits",
dur,numbits,(int)level);
/* If the signal is too short, let's claim it an interference
* and ignore it completely. */
if(numbits == 0) continue;
if (numbits == 0) continue;
while(numbits--) bitmap_set(b, blen, bitpos++, level);
while(numbits--) bitmap_set(b,blen,bitpos++,level);
}
return bitpos;
}
@@ -345,29 +340,23 @@ uint32_t convert_signal_to_bits(
* specified in bytes by the caller, via the 'len' parameters).
*
* The decoding starts at the specified offset (in bits) 'off'. */
uint32_t convert_from_line_code(
uint8_t* buf,
uint64_t buflen,
uint8_t* bits,
uint32_t len,
uint32_t off,
const char* zero_pattern,
const char* one_pattern) {
uint32_t convert_from_line_code(uint8_t *buf, uint64_t buflen, uint8_t *bits, uint32_t len, uint32_t off, const char *zero_pattern, const char *one_pattern)
{
uint32_t decoded = 0; /* Number of bits extracted. */
len *= 8; /* Convert bytes to bits. */
while(off < len) {
bool bitval;
if(bitmap_match_bits(bits, len, off, zero_pattern)) {
if (bitmap_match_bits(bits,len,off,zero_pattern)) {
bitval = false;
off += strlen(zero_pattern);
} else if(bitmap_match_bits(bits, len, off, one_pattern)) {
} else if (bitmap_match_bits(bits,len,off,one_pattern)) {
bitval = true;
off += strlen(one_pattern);
} else {
break;
}
bitmap_set(buf, buflen, decoded++, bitval);
if(decoded / 8 == buflen) break; /* No space left on target buffer. */
bitmap_set(buf,buflen,decoded++,bitval);
if (decoded/8 == buflen) break; /* No space left on target buffer. */
}
return decoded;
}
@@ -378,22 +367,17 @@ uint32_t convert_from_line_code(
* in differential codings the next bits depend on the previous one.
*
* 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;
len *= 8; /* Conver to bits. */
for(uint32_t j = off; j < len; j += 2) {
bool b0 = bitmap_get(bits, len, j);
bool b1 = bitmap_get(bits, len, j + 1);
if(b0 == previous) break; /* Each new bit must switch value. */
bitmap_set(buf, buflen, decoded++, b0 == b1);
for (uint32_t j = off; j < len; j += 2) {
bool b0 = bitmap_get(bits,len,j);
bool b1 = bitmap_get(bits,len,j+1);
if (b0 == previous) break; /* Each new bit must switch value. */
bitmap_set(buf,buflen,decoded++,b0 == 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;
}
@@ -406,52 +390,51 @@ extern ProtoViewDecoder B4B1Decoder;
extern ProtoViewDecoder RenaultTPMSDecoder;
extern ProtoViewDecoder ToyotaTPMSDecoder;
extern ProtoViewDecoder SchraderTPMSDecoder;
extern ProtoViewDecoder SchraderEG53MA4TPMSDecoder;
extern ProtoViewDecoder CitroenTPMSDecoder;
extern ProtoViewDecoder FordTPMSDecoder;
extern ProtoViewDecoder KeeloqDecoder;
ProtoViewDecoder* Decoders[] = {
&Oregon2Decoder, /* Oregon sensors v2.1 protocol. */
&B4B1Decoder, /* PT, SC, ... 24 bits remotes. */
&RenaultTPMSDecoder, /* Renault TPMS. */
&ToyotaTPMSDecoder, /* Toyota TPMS. */
&SchraderTPMSDecoder, /* Schrader TPMS. */
&CitroenTPMSDecoder, /* Citroen TPMS. */
&FordTPMSDecoder, /* Ford TPMS. */
NULL};
ProtoViewDecoder *Decoders[] = {
&Oregon2Decoder, /* Oregon sensors v2.1 protocol. */
&B4B1Decoder, /* PT, SC, ... 24 bits remotes. */
&RenaultTPMSDecoder, /* Renault TPMS. */
&ToyotaTPMSDecoder, /* Toyota TPMS. */
&SchraderTPMSDecoder, /* Schrader TPMS. */
&SchraderEG53MA4TPMSDecoder, /* Schrader EG53MA4 TPMS. */
&CitroenTPMSDecoder, /* Citroen TPMS. */
&FordTPMSDecoder, /* Ford TPMS. */
&KeeloqDecoder, /* Keeloq remote. */
NULL
};
/* Reset the message info structure before passing it to the decoding
* functions. */
void initialize_msg_info(ProtoViewMsgInfo* i) {
memset(i, 0, sizeof(ProtoViewMsgInfo));
void initialize_msg_info(ProtoViewMsgInfo *i) {
memset(i,0,sizeof(ProtoViewMsgInfo));
}
/* This function is called when a new signal is detected. It converts it
* to a bitstream, and the calls the protocol specific functions for
* decoding. If the signal was decoded correctly by some protocol, true
* is returned. Otherwise false is returned. */
bool decode_signal(RawSamplesBuffer* s, uint64_t len, ProtoViewMsgInfo* info) {
uint32_t bitmap_bits_size = 4096 * 8;
uint32_t bitmap_size = bitmap_bits_size / 8;
bool decode_signal(RawSamplesBuffer *s, uint64_t len, ProtoViewMsgInfo *info) {
uint32_t bitmap_bits_size = 4096*8;
uint32_t bitmap_size = bitmap_bits_size/8;
/* We call the decoders with an offset a few samples before the actual
* signal detected and for a len of a few bits after its end. */
uint32_t before_samples = 20;
uint32_t after_samples = 100;
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);
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);
if(DEBUG_MSG) { /* Useful for debugging purposes. Don't remove. */
char* str = malloc(1024);
if (DEBUG_MSG) { /* Useful for debugging purposes. Don't remove. */
char *str = malloc(1024);
uint32_t j;
for(j = 0; j < bits && j < 1023; j++) {
str[j] = bitmap_get(bitmap, bitmap_size, j) ? '1' : '0';
for (j = 0; j < bits && j < 1023; j++) {
str[j] = bitmap_get(bitmap,bitmap_size,j) ? '1' : '0';
}
str[j] = 0;
FURI_LOG_E(TAG, "%lu bits sampled: %s", bits, str);
@@ -464,25 +447,18 @@ bool decode_signal(RawSamplesBuffer* s, uint64_t len, ProtoViewMsgInfo* info) {
bool decoded = false;
while(Decoders[j]) {
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;
FURI_LOG_E(TAG, "Decoder %s took %lu ms", Decoders[j]->name, (unsigned long)delta);
if(decoded) break;
FURI_LOG_E(TAG, "Decoder %s took %lu ms",
Decoders[j]->name, (unsigned long)delta);
if (decoded) break;
j++;
}
if(!decoded) {
if (!decoded) {
FURI_LOG_E(TAG, "No decoding possible");
} else {
FURI_LOG_E(
TAG,
"Decoded %s, raw=%s info=[%s,%s,%s,%s]",
info->name,
info->raw,
info->info1,
info->info2,
info->info3,
info->info4);
FURI_LOG_E(TAG, "Decoded %s, raw=%s info=[%s,%s,%s,%s]", info->name, info->raw, info->info1, info->info2, info->info3, info->info4);
}
free(bitmap);
return decoded;
+16 -12
View File
@@ -3,24 +3,28 @@
#include "app.h"
void canvas_draw_str_with_border(
Canvas* canvas,
uint8_t x,
uint8_t y,
const char* str,
Color text_color,
Color border_color) {
void canvas_draw_str_with_border(Canvas* canvas, uint8_t x, uint8_t y, const char* str, Color text_color, Color border_color)
{
struct {
uint8_t x;
uint8_t y;
} dir[8] = {{-1, -1}, {0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}};
uint8_t x; uint8_t y;
} dir[8] = {
{-1,-1},
{0,-1},
{1,-1},
{1,0},
{1,1},
{0,1},
{-1,1},
{-1,0}
};
/* Rotate in all the directions writing the same string to create a
* border, then write the actual string in the other color in the
* middle. */
canvas_set_color(canvas, border_color);
for(int j = 0; j < 8; j++) canvas_draw_str(canvas, x + dir[j].x, y + dir[j].y, str);
for (int j = 0; j < 8; j++)
canvas_draw_str(canvas,x+dir[j].x,y+dir[j].y,str);
canvas_set_color(canvas, text_color);
canvas_draw_str(canvas, x, y, str);
canvas_draw_str(canvas,x,y,str);
canvas_set_color(canvas, ColorBlack);
}
@@ -7,46 +7,47 @@
/* Read directly from the G0 CC1101 pin, and draw a black or white
* dot depending on the level. */
void render_view_direct_sampling(Canvas* const canvas, ProtoViewApp* app) {
if(!app->direct_sampling_enabled) {
void render_view_direct_sampling(Canvas *const canvas, ProtoViewApp *app) {
if (!app->direct_sampling_enabled) {
canvas_set_font(canvas, FontSecondary);
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, 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, 45, "Can crash your Flipper.");
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,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,45,"Can crash your Flipper.");
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;
}
for(int y = 0; y < 64; y++) {
for(int x = 0; x < 128; x++) {
for (int y = 0; y < 64; y++) {
for (int x = 0; x < 128; x++) {
bool level = furi_hal_gpio_read(&gpio_cc1101_g0);
if(level) canvas_draw_dot(canvas, x, y);
if (level) canvas_draw_dot(canvas,x,y);
/* Busy loop: this is a terrible approach as it blocks
* everything else, but for now it's the best we can do
* to obtain direct data with some spacing. */
uint32_t x = 250;
while(x--)
;
uint32_t x = 250; while(x--);
}
}
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_with_border(canvas, 36, 60, "Direct sampling", ColorWhite, ColorBlack);
canvas_draw_str_with_border(canvas,36,60,"Direct sampling",
ColorWhite,ColorBlack);
}
/* Handle input */
void process_input_direct_sampling(ProtoViewApp* app, InputEvent input) {
if(input.type == InputTypePress && input.key == InputKeyOk) {
void process_input_direct_sampling(ProtoViewApp *app, InputEvent input) {
if (input.type == InputTypePress && input.key == InputKeyOk) {
app->direct_sampling_enabled = !app->direct_sampling_enabled;
}
}
/* Enter view. Stop the subghz thread to prevent access as we read
* the CC1101 data directly. */
void view_enter_direct_sampling(ProtoViewApp* app) {
if(app->txrx->txrx_state == TxRxStateRx && !app->txrx->debug_timer_sampling) {
void view_enter_direct_sampling(ProtoViewApp *app) {
if (app->txrx->txrx_state == TxRxStateRx &&
!app->txrx->debug_timer_sampling)
{
subghz_worker_stop(app->txrx->worker);
} else {
raw_sampling_worker_stop(app);
@@ -54,8 +55,10 @@ void view_enter_direct_sampling(ProtoViewApp* app) {
}
/* Exit view. Restore the subghz thread. */
void view_exit_direct_sampling(ProtoViewApp* app) {
if(app->txrx->txrx_state == TxRxStateRx && !app->txrx->debug_timer_sampling) {
void view_exit_direct_sampling(ProtoViewApp *app) {
if (app->txrx->txrx_state == TxRxStateRx &&
!app->txrx->debug_timer_sampling)
{
subghz_worker_start(app->txrx->worker);
} else {
raw_sampling_worker_start(app);
+12 -16
View File
@@ -4,10 +4,10 @@
#include "app.h"
/* Renders the view with the detected message information. */
void render_view_info(Canvas* const canvas, ProtoViewApp* app) {
if(app->signal_decoded == false) {
void render_view_info(Canvas *const canvas, ProtoViewApp *app) {
if (app->signal_decoded == false) {
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 30, 36, "No signal decoded");
canvas_draw_str(canvas, 30,36,"No signal decoded");
return;
}
@@ -20,25 +20,21 @@ void render_view_info(Canvas* const canvas, ProtoViewApp* app) {
/* Info fields. */
char buf[128];
canvas_set_font(canvas, FontSecondary);
if(app->signal_info.raw[0]) {
snprintf(buf, sizeof(buf), "Raw: %s", app->signal_info.raw);
if (app->signal_info.raw[0]) {
snprintf(buf,sizeof(buf),"Raw: %s", app->signal_info.raw);
canvas_draw_str(canvas, 0, y, buf);
y += lineheight;
}
canvas_draw_str(canvas, 0, y, app->signal_info.info1);
y += lineheight;
canvas_draw_str(canvas, 0, y, app->signal_info.info2);
y += lineheight;
canvas_draw_str(canvas, 0, y, app->signal_info.info3);
y += lineheight;
canvas_draw_str(canvas, 0, y, app->signal_info.info4);
y += lineheight;
canvas_draw_str(canvas, 0, y, app->signal_info.info1); y += lineheight;
canvas_draw_str(canvas, 0, y, app->signal_info.info2); y += lineheight;
canvas_draw_str(canvas, 0, y, app->signal_info.info3); y += lineheight;
canvas_draw_str(canvas, 0, y, app->signal_info.info4); y += lineheight;
}
/* Handle input for the info view. */
void process_input_info(ProtoViewApp* app, InputEvent input) {
if(input.type == InputTypeShort) {
if(input.key == InputKeyOk) {
void process_input_info(ProtoViewApp *app, InputEvent input) {
if (input.type == InputTypeShort) {
if (input.key == InputKeyOk) {
/* Reset the current sample to capture the next. */
reset_current_signal(app);
}
@@ -12,7 +12,7 @@
*
* The 'idx' argument is the first sample to render in the circular
* buffer. */
void render_signal(ProtoViewApp* app, Canvas* const canvas, RawSamplesBuffer* buf, uint32_t idx) {
void render_signal(ProtoViewApp *app, Canvas *const canvas, RawSamplesBuffer *buf, uint32_t idx) {
canvas_set_color(canvas, ColorBlack);
int rows = 8;
@@ -20,29 +20,31 @@ void render_signal(ProtoViewApp* app, Canvas* const canvas, RawSamplesBuffer* bu
uint32_t start_idx = idx;
bool level = 0;
uint32_t dur = 0, sample_num = 0;
for(int row = 0; row < rows; row++) {
for(int x = 0; x < 128; x++) {
int y = 3 + row * 8;
if(dur < time_per_pixel / 2) {
for (int row = 0; row < rows ; row++) {
for (int x = 0; x < 128; x++) {
int y = 3 + row*8;
if (dur < time_per_pixel/2) {
/* Get more data. */
raw_samples_get(buf, idx++, &level, &dur);
sample_num++;
}
canvas_draw_line(canvas, x, y, x, y - (level * 3));
canvas_draw_line(canvas, x,y,x,y-(level*3));
/* Write a small triangle under the last sample detected. */
if(app->signal_bestlen != 0 && sample_num + start_idx == app->signal_bestlen + 1) {
canvas_draw_dot(canvas, x, y + 2);
canvas_draw_dot(canvas, x - 1, y + 3);
canvas_draw_dot(canvas, x, y + 3);
canvas_draw_dot(canvas, x + 1, y + 3);
if (app->signal_bestlen != 0 &&
sample_num+start_idx == app->signal_bestlen+1)
{
canvas_draw_dot(canvas,x,y+2);
canvas_draw_dot(canvas,x-1,y+3);
canvas_draw_dot(canvas,x,y+3);
canvas_draw_dot(canvas,x+1,y+3);
sample_num++; /* Make sure we don't mark the next, too. */
}
/* Remove from the current level duration the time we
* just plot. */
if(dur > time_per_pixel)
if (dur > time_per_pixel)
dur -= time_per_pixel;
else
dur = 0;
@@ -51,46 +53,45 @@ void render_signal(ProtoViewApp* app, Canvas* const canvas, RawSamplesBuffer* bu
}
/* 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. */
render_signal(app, canvas, DetectedSamples, app->signal_offset);
/* Show signal information. */
char buf[64];
snprintf(buf, sizeof(buf), "%luus", (unsigned long)DetectedSamples->short_pulse_dur);
snprintf(buf,sizeof(buf),"%luus",
(unsigned long)DetectedSamples->short_pulse_dur);
canvas_set_font(canvas, FontSecondary);
canvas_draw_str_with_border(canvas, 97, 63, buf, ColorWhite, ColorBlack);
if(app->signal_decoded) {
if (app->signal_decoded) {
canvas_set_font(canvas, FontPrimary);
canvas_draw_str_with_border(canvas, 1, 61, app->signal_info.name, ColorWhite, ColorBlack);
}
}
/* Handle input for the raw pulses view. */
void process_input_raw_pulses(ProtoViewApp* app, InputEvent input) {
if(input.type == InputTypeRepeat) {
void process_input_raw_pulses(ProtoViewApp *app, InputEvent input) {
if (input.type == InputTypeRepeat) {
/* Handle panning of the signal window. Long pressing
* right will show successive samples, long pressing left
* previous samples. */
if(input.key == InputKeyRight)
app->signal_offset++;
else if(input.key == InputKeyLeft)
app->signal_offset--;
else if(input.key == InputKeyOk) {
if (input.key == InputKeyRight) app->signal_offset++;
else if (input.key == InputKeyLeft) app->signal_offset--;
else if (input.key == InputKeyOk) {
app->signal_offset = 0;
app->us_scale = PROTOVIEW_RAW_VIEW_DEFAULT_SCALE;
}
} else if(input.type == InputTypeShort) {
if(input.key == InputKeyOk) {
} else if (input.type == InputTypeShort) {
if (input.key == InputKeyOk) {
/* Reset the current sample to capture the next. */
reset_current_signal(app);
} else if(input.key == InputKeyDown) {
} else if (input.key == InputKeyDown) {
/* Rescaling. The set becomes finer under 50us per pixel. */
uint32_t scale_step = app->us_scale >= 50 ? 50 : 10;
if(app->us_scale < 500) app->us_scale += scale_step;
} else if(input.key == InputKeyUp) {
if (app->us_scale < 500) app->us_scale += scale_step;
} else if (input.key == InputKeyUp) {
uint32_t scale_step = app->us_scale > 50 ? 50 : 10;
if(app->us_scale > 10) app->us_scale -= scale_step;
if (app->us_scale > 10) app->us_scale -= scale_step;
}
}
}
+36 -38
View File
@@ -6,30 +6,30 @@
/* Renders a single view with frequency and modulation setting. However
* this are logically two different views, and only one of the settings
* will be highlighted. */
void render_view_settings(Canvas* const canvas, ProtoViewApp* app) {
void render_view_settings(Canvas *const canvas, ProtoViewApp *app) {
canvas_set_font(canvas, FontPrimary);
if(app->current_view == ViewFrequencySettings)
canvas_draw_str_with_border(canvas, 1, 10, "Frequency", ColorWhite, ColorBlack);
if (app->current_view == ViewFrequencySettings)
canvas_draw_str_with_border(canvas,1,10,"Frequency",ColorWhite,ColorBlack);
else
canvas_draw_str(canvas, 1, 10, "Frequency");
canvas_draw_str(canvas,1,10,"Frequency");
if(app->current_view == ViewModulationSettings)
canvas_draw_str_with_border(canvas, 70, 10, "Modulation", ColorWhite, ColorBlack);
if (app->current_view == ViewModulationSettings)
canvas_draw_str_with_border(canvas,70,10,"Modulation",ColorWhite,ColorBlack);
else
canvas_draw_str(canvas, 70, 10, "Modulation");
canvas_draw_str(canvas,70,10,"Modulation");
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)
canvas_draw_str(canvas, 3, 52, "(DEBUG timer sampling is ON)");
if (app->txrx->debug_timer_sampling)
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. */
if(app->current_view == ViewFrequencySettings) {
if (app->current_view == ViewFrequencySettings) {
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_draw_str(canvas, 30, 40, buf);
} else if(app->current_view == ViewModulationSettings) {
} else if (app->current_view == ViewModulationSettings) {
int current = app->modulation;
canvas_set_font(canvas, FontPrimary);
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. */
void process_input_settings(ProtoViewApp* app, InputEvent input) {
if(input.type == InputTypeLong && input.key == InputKeyOk) {
void process_input_settings(ProtoViewApp *app, InputEvent input) {
if (input.type == InputTypeLong && input.key == InputKeyOk) {
/* Long pressing to OK sets the default frequency and
* modulation. */
app->frequency = subghz_setting_get_default_frequency(app->setting);
app->modulation = 0;
} else if(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
* timer sampling mode. NOTE: this feature is disabled for users,
* only useful for devs (if useful at all). */
@@ -55,40 +55,42 @@ void process_input_settings(ProtoViewApp* app, InputEvent input) {
app->txrx->debug_timer_sampling = !app->txrx->debug_timer_sampling;
radio_begin(app);
radio_rx(app);
} else if(input.type == InputTypePress && (input.key != InputKeyDown || input.key != InputKeyUp)) {
} else if (input.type == InputTypePress &&
(input.key != InputKeyDown || input.key != InputKeyUp))
{
/* Handle up and down to change frequency or modulation. */
if(app->current_view == ViewFrequencySettings) {
if (app->current_view == ViewFrequencySettings) {
size_t curidx = 0, i;
size_t count = subghz_setting_get_frequency_count(app->setting);
/* Scan the list of frequencies to check for the index of the
* currently set frequency. */
for(i = 0; i < count; i++) {
uint32_t freq = subghz_setting_get_frequency(app->setting, i);
if(freq == app->frequency) {
uint32_t freq = subghz_setting_get_frequency(app->setting,i);
if (freq == app->frequency) {
curidx = i;
break;
}
}
if(i == count) return; /* Should never happen. */
if (i == count) return; /* Should never happen. */
if(input.key == InputKeyUp) {
curidx = curidx == 0 ? count - 1 : curidx - 1;
} else if(input.key == InputKeyDown) {
curidx = (curidx + 1) % count;
if (input.key == InputKeyUp) {
curidx = curidx == 0 ? count-1 : curidx-1;
} else if (input.key == InputKeyDown) {
curidx = (curidx+1) % count;
} else {
return;
}
app->frequency = subghz_setting_get_frequency(app->setting, curidx);
} else if(app->current_view == ViewModulationSettings) {
app->frequency = subghz_setting_get_frequency(app->setting,curidx);
} else if (app->current_view == ViewModulationSettings) {
uint32_t count = 0;
uint32_t modid = app->modulation;
while(ProtoViewModulations[count].name != NULL) count++;
if(input.key == InputKeyUp) {
modid = modid == 0 ? count - 1 : modid - 1;
} else if(input.key == InputKeyDown) {
modid = (modid + 1) % count;
if (input.key == InputKeyUp) {
modid = modid == 0 ? count-1 : modid-1;
} else if (input.key == InputKeyDown) {
modid = (modid+1) % count;
} else {
return;
}
@@ -104,13 +106,9 @@ void process_input_settings(ProtoViewApp* app, InputEvent input) {
/* 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. */
void view_exit_settings(ProtoViewApp* app) {
if(app->txrx->freq_mod_changed) {
FURI_LOG_E(
TAG,
"Setting view, setting frequency/modulation to %lu %s",
app->frequency,
ProtoViewModulations[app->modulation].name);
void view_exit_settings(ProtoViewApp *app) {
if (app->txrx->freq_mod_changed) {
FURI_LOG_E(TAG, "Setting view, setting frequency/modulation to %lu %s", app->frequency, ProtoViewModulations[app->modulation].name);
radio_rx_end(app);
radio_begin(app);
radio_rx(app);