mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-06-19 20:34:19 -07:00
updated protoview
This commit is contained in:
@@ -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 :)
|
||||
|
||||

|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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},
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user