updated protoview and fixed array count issue

arrays start at 0 and citroen_tpms was trying to read 0-10 meaning 11 full bytes rather than actually 10 (0-9).
This commit is contained in:
jbohack
2023-01-13 10:02:14 -05:00
parent 332abdacbb
commit 6015afe368
23 changed files with 663 additions and 89 deletions

View File

@@ -6,11 +6,24 @@ the car keys), the curious person is left wondering what the device is
sending at all. Using ProtoView she or he can visualize the high and low pulses sending at all. Using ProtoView she or he can visualize the high and low pulses
like in the example image below (showing a Volkswagen key in 2FSK): like in the example image below (showing a Volkswagen key in 2FSK):
![ProtoView screenshot](/images/ProtoViewSignal.jpg) ![ProtoView screenshot raw signal](/images/protoview_1.jpg)
This is often enough to make an initial idea about the encoding used This is often enough to make an initial idea about the encoding used
and if the selected modulation is correct. 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.
* Oregon thermometer protocol 2.
* PTxxxx/SCxxxx based remotes.
* ... more will be implemented soon, hopefully. Send PRs :)
![ProtoView screenshot Renault TPMS data](/images/protoview_2.jpg)
The app implements a framework that makes adding and experimenting with new
protocols very simple. Check the `protocols` directory to see how the
API works.
The secondary goal of ProtoView is to provide a somewhat-documented application The secondary goal of ProtoView is to provide a somewhat-documented application
for the Flipper (even if ProtoView is a pretty atypical application: doesn't make use of the standard widgets and other abstractions provded by the framework). for the Flipper (even if ProtoView is a pretty atypical application: doesn't make use of the standard widgets and other abstractions provded by the framework).
Many apps dealing with the *subghz subsystem* (the Flipper Many apps dealing with the *subghz subsystem* (the Flipper
@@ -40,7 +53,7 @@ encodings are somewhat self-clocked, so they tend to have just two or
three classes of pulse lengths. three classes of pulse lengths.
However often pulses of the same theoretical However often pulses of the same theoretical
length have slightly different lenghts in the case of high and low level length have slightly different lengths in the case of high and low level
(RF on or off), so we classify them separately for robustness. (RF on or off), so we classify them separately for robustness.
# Usage # Usage
@@ -55,6 +68,10 @@ Under the detected sequence, you will see a small triangle marking a
specific sample. This mark means that the sequence looked coherent up specific sample. This mark means that the sequence looked coherent up
to that point, and starting from there it could be just noise. to that point, and starting from there it could be just noise.
If the protocol is decoded, the bottom-left corner of the screen
will show the name of the protocol, and going in the next screen
with the right arrow will show information about the decoded signal.
In the bottom-right corner the application displays an amount of time In the bottom-right corner the application displays an amount of time
in microseconds. This is the average length of the shortest pulse length in microseconds. This is the average length of the shortest pulse length
detected among the three classes. Usually the *data rate* of the protocol detected among the three classes. Usually the *data rate* of the protocol
@@ -67,7 +84,8 @@ Things to investigate:
* Many cheap remotes (gate openers, remotes, ...) are on the 433.92Mhz or nearby and use OOK modulation. * Many cheap remotes (gate openers, remotes, ...) are on the 433.92Mhz or nearby and use OOK modulation.
* Weather stations are often too in the 433.92Mhz OOK. * Weather stations are often too in the 433.92Mhz OOK.
* For car keys, try 443.92 OOK650 and 868.35 Mhz in OOK or 2FSK. * For car keys, try 433.92 OOK650 and 868.35 Mhz in OOK or 2FSK.
* For TPMS try 433.92 in TPMS modulation (FSK optimized for these signals).
# Installing the app from source # Installing the app from source
@@ -101,3 +119,11 @@ The code is released under the BSD license.
# Disclaimer # Disclaimer
This application is only provided as an educational tool. The author is not liable in case the application is used to reverse engineer protocols protected by IP or for any other illegal purpose. This application is only provided as an educational tool. The author is not liable in case the application is used to reverse engineer protocols protected by IP or for any other illegal purpose.
# Credits
A big thank you to the RTL433 author, [Benjamin Larsson](https://github.com/merbanan). I used the code and tools he developed in many ways:
* To capture TPMS data with rtl433 and save to a file, to later play the IQ files and speedup the development.
* As a sourve of documentation for protocols.
* As an awesome way to visualize and understand protocols, via [these great web tools](https://triq.org/).
* To have tons of fun with RTLSDR in general, now and in the past.

View File

@@ -1,20 +1,14 @@
Core improvements Core improvements
================= =================
- Detection of non Manchester and non RZ encoded signals. Not sure if there are any signals that are not self clocked widely used in RF. Note that the current approach already detects encodings using short high + long low and long high + short low to encode 0 and 1. In addition to the current classifier, it is possible to add one that checks for a sequence of pulses that are all multiples of some base length. This should detect, for instance, even NRZ encodings where 1 and 0 are just clocked as they are. - Decoders should declare the short pulse duration range, so that
only matching decoders will be called. This may also be useful for
- Views on-enter on-exit. modulations. If a signal is only OOK, does not make much sense to
call it for samples obtained in FSK.
Features - More protocols, especially TPMS and other stuff not supported right now
======== by the Flipper.
- CC1101 synchronous mode with protocol hopping?
- Help screen (with press ok for next page). - Protocols decoded can register actions, for instance to generate
- Detect the line code used and try to decode the message as hex dump. sub files with modified signal and so forth.
- Pressing right/left you browse different modes: - Optimize memory usage storing raw samples in a bitfield: 15 bits
* Current best signal pulse classes. duration, 1 bit level.
* Raw square wave display. Central button freezes and resumes (toggle). When frozen we display "paused" (inverted) on the low part of the screen.
Screens sequence (user can navigate with <- and ->):
(default)
[settings] <> [freq] <> [pulses view] <> [raw square view] <> [signal info]

View File

@@ -89,6 +89,12 @@ static void app_switch_view(ProtoViewApp *app, SwitchViewDirection dir) {
/* Call the enter/exit view callbacks if needed. */ /* Call the enter/exit view callbacks if needed. */
if (old == ViewDirectSampling) view_exit_direct_sampling(app); if (old == ViewDirectSampling) view_exit_direct_sampling(app);
if (new == ViewDirectSampling) view_enter_direct_sampling(app); if (new == ViewDirectSampling) view_enter_direct_sampling(app);
/* The frequency/modulation settings are actually a single view:
* 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))
view_exit_settings(app);
} }
/* Allocate the application state and initialize a number of stuff. /* Allocate the application state and initialize a number of stuff.
@@ -112,9 +118,11 @@ ProtoViewApp* protoview_app_alloc() {
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen); gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent)); app->event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
app->current_view = ViewRawPulses; app->current_view = ViewRawPulses;
app->direct_sampling_enabled = false;
// Signal found and visualization defaults // Signal found and visualization defaults
app->signal_bestlen = 0; app->signal_bestlen = 0;
app->signal_last_scan_idx = 0;
app->signal_decoded = false; app->signal_decoded = false;
app->us_scale = PROTOVIEW_RAW_VIEW_DEFAULT_SCALE; app->us_scale = PROTOVIEW_RAW_VIEW_DEFAULT_SCALE;
app->signal_offset = 0; app->signal_offset = 0;
@@ -123,20 +131,24 @@ ProtoViewApp* protoview_app_alloc() {
app->txrx = malloc(sizeof(ProtoViewTxRx)); app->txrx = malloc(sizeof(ProtoViewTxRx));
/* Setup rx worker and environment. */ /* Setup rx worker and environment. */
app->txrx->freq_mod_changed = false;
app->txrx->debug_timer_sampling = false;
app->txrx->last_g0_change_time = DWT->CYCCNT;
app->txrx->last_g0_value = false;
app->txrx->worker = subghz_worker_alloc(); app->txrx->worker = subghz_worker_alloc();
#ifdef PROTOVIEW_DISABLE_SUBGHZ_FILTER #ifdef PROTOVIEW_DISABLE_SUBGHZ_FILTER
app->txrx->worker->filter_running = 0; app->txrx->worker->filter_running = 0;
#endif #endif
app->txrx->environment = subghz_environment_alloc(); app->txrx->environment = subghz_environment_alloc();
subghz_environment_set_protocol_registry( subghz_environment_set_protocol_registry(
app->txrx->environment, (void*)&protoview_protocol_registry); app->txrx->environment, (void*)&protoview_protocol_registry);
app->txrx->receiver = subghz_receiver_alloc_init(app->txrx->environment); app->txrx->receiver =
subghz_receiver_alloc_init(app->txrx->environment);
subghz_receiver_set_filter(app->txrx->receiver, SubGhzProtocolFlag_Decodable); subghz_receiver_set_filter(app->txrx->receiver,
SubGhzProtocolFlag_Decodable);
subghz_worker_set_overrun_callback( subghz_worker_set_overrun_callback(
app->txrx->worker, (SubGhzWorkerOverrunCallback)subghz_receiver_reset); app->txrx->worker,
(SubGhzWorkerOverrunCallback)subghz_receiver_reset);
subghz_worker_set_pair_callback( subghz_worker_set_pair_callback(
app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode); app->txrx->worker, (SubGhzWorkerPairCallback)subghz_receiver_decode);
subghz_worker_set_context(app->txrx->worker, app->txrx->receiver); subghz_worker_set_context(app->txrx->worker, app->txrx->receiver);
@@ -171,9 +183,11 @@ void protoview_app_free(ProtoViewApp *app) {
subghz_setting_free(app->setting); subghz_setting_free(app->setting);
// Worker stuff. // Worker stuff.
subghz_receiver_free(app->txrx->receiver); if (!app->txrx->debug_timer_sampling) {
subghz_environment_free(app->txrx->environment); subghz_receiver_free(app->txrx->receiver);
subghz_worker_free(app->txrx->worker); subghz_environment_free(app->txrx->environment);
subghz_worker_free(app->txrx->worker);
}
free(app->txrx); free(app->txrx);
// Raw samples buffers. // Raw samples buffers.
@@ -189,6 +203,20 @@ void protoview_app_free(ProtoViewApp *app) {
* function is to scan for signals and set DetectedSamples. */ * function is to scan for signals and set DetectedSamples. */
static void timer_callback(void *ctx) { static void timer_callback(void *ctx) {
ProtoViewApp *app = ctx; ProtoViewApp *app = ctx;
uint32_t delta, lastidx = app->signal_last_scan_idx;
/* scan_for_signal(), called by this function, deals with a
* circular buffer. To never miss anything, even if a signal spawns
* 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) {
delta = RawSamples->idx - lastidx;
} else {
delta = RawSamples->total - lastidx + RawSamples->idx;
}
if (delta < RawSamples->total/2) return;
app->signal_last_scan_idx = RawSamples->idx;
scan_for_signal(app); scan_for_signal(app);
} }
@@ -198,7 +226,7 @@ int32_t protoview_app_entry(void* p) {
/* Create a timer. We do data analysis in the callback. */ /* Create a timer. We do data analysis in the callback. */
FuriTimer *timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, app); FuriTimer *timer = furi_timer_alloc(timer_callback, FuriTimerTypePeriodic, app);
furi_timer_start(timer, furi_kernel_get_tick_frequency() / 4); furi_timer_start(timer, furi_kernel_get_tick_frequency() / 8);
/* Start listening to signals immediately. */ /* Start listening to signals immediately. */
radio_begin(app); radio_begin(app);

View File

@@ -65,10 +65,21 @@ extern ProtoViewModulation ProtoViewModulations[]; /* In app_subghz.c */
* It receives data and we get our protocol "feed" callback called * It receives data and we get our protocol "feed" callback called
* with the level (1 or 0) and duration. */ * with the level (1 or 0) and duration. */
struct ProtoViewTxRx { struct ProtoViewTxRx {
bool freq_mod_changed; /* The user changed frequency and/or modulation
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; SubGhzEnvironment* environment;
SubGhzReceiver* receiver; SubGhzReceiver* receiver;
TxRxState txrx_state; /* Receiving, idle or sleeping? */ TxRxState txrx_state; /* Receiving, idle or sleeping? */
/* Timer sampling mode state. */
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
checking the duration in the timer
handler. */
}; };
typedef struct ProtoViewTxRx ProtoViewTxRx; typedef struct ProtoViewTxRx ProtoViewTxRx;
@@ -85,6 +96,7 @@ typedef struct ProtoViewMsgInfo {
char info1[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 1. */ char info1[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 1. */
char info2[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 2. */ char info2[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 2. */
char info3[PROTOVIEW_MSG_STR_LEN]; /* Protocol specific info line 3. */ 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; } ProtoViewMsgInfo;
@@ -103,8 +115,12 @@ struct ProtoViewApp {
/* Generic app state. */ /* Generic app state. */
int running; /* Once false exists the app. */ int running; /* Once false exists the app. */
uint32_t signal_bestlen; /* Longest coherent signal observed so far. */ uint32_t signal_bestlen; /* Longest coherent signal observed so far. */
uint32_t signal_last_scan_idx; /* Index of the buffer last time we
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. */ 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. */ /* Raw view apps state. */
uint32_t us_scale; /* microseconds per pixel. */ uint32_t us_scale; /* microseconds per pixel. */
@@ -136,6 +152,8 @@ uint32_t radio_rx(ProtoViewApp* app);
void radio_idle(ProtoViewApp* app); void radio_idle(ProtoViewApp* app);
void radio_rx_end(ProtoViewApp* app); void radio_rx_end(ProtoViewApp* app);
void radio_sleep(ProtoViewApp* app); void radio_sleep(ProtoViewApp* app);
void raw_sampling_worker_start(ProtoViewApp *app);
void raw_sampling_worker_stop(ProtoViewApp *app);
/* signal.c */ /* signal.c */
uint32_t duration_delta(uint32_t a, uint32_t b); uint32_t duration_delta(uint32_t a, uint32_t b);
@@ -148,6 +166,7 @@ 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); 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 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_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 */ /* view_*.c */
void render_view_raw_pulses(Canvas *const canvas, ProtoViewApp *app); void render_view_raw_pulses(Canvas *const canvas, ProtoViewApp *app);
@@ -160,6 +179,10 @@ void render_view_direct_sampling(Canvas *const canvas, ProtoViewApp *app);
void process_input_direct_sampling(ProtoViewApp *app, InputEvent input); void process_input_direct_sampling(ProtoViewApp *app, InputEvent input);
void view_enter_direct_sampling(ProtoViewApp *app); void view_enter_direct_sampling(ProtoViewApp *app);
void view_exit_direct_sampling(ProtoViewApp *app); void view_exit_direct_sampling(ProtoViewApp *app);
void view_exit_settings(ProtoViewApp *app);
/* ui.c */ /* 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);

View File

@@ -5,6 +5,12 @@
#include "custom_presets.h" #include "custom_presets.h"
#include <flipper_format/flipper_format_i.h> #include <flipper_format/flipper_format_i.h>
#include <furi_hal_rtc.h>
#include <furi_hal_spi.h>
#include <furi_hal_interrupt.h>
void raw_sampling_worker_start(ProtoViewApp *app);
void raw_sampling_worker_stop(ProtoViewApp *app);
ProtoViewModulation ProtoViewModulations[] = { ProtoViewModulation ProtoViewModulations[] = {
{"OOK 650Khz", FuriHalSubGhzPresetOok650Async, NULL}, {"OOK 650Khz", FuriHalSubGhzPresetOok650Async, NULL},
@@ -15,6 +21,7 @@ ProtoViewModulation ProtoViewModulations[] = {
{"GFSK", FuriHalSubGhzPresetGFSK9_99KbAsync, NULL}, {"GFSK", FuriHalSubGhzPresetGFSK9_99KbAsync, NULL},
{"TPMS 1 (FSK)", 0, (uint8_t*)protoview_subghz_tpms1_async_regs}, {"TPMS 1 (FSK)", 0, (uint8_t*)protoview_subghz_tpms1_async_regs},
{"TPMS 2 (FSK)", 0, (uint8_t*)protoview_subghz_tpms2_async_regs}, {"TPMS 2 (FSK)", 0, (uint8_t*)protoview_subghz_tpms2_async_regs},
{"TPMS 3 (FSK)", 0, (uint8_t*)protoview_subghz_tpms3_async_regs},
{NULL, 0, NULL} /* End of list sentinel. */ {NULL, 0, NULL} /* End of list sentinel. */
}; };
@@ -53,9 +60,14 @@ uint32_t radio_rx(ProtoViewApp* app) {
furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow); furi_hal_gpio_init(&gpio_cc1101_g0, GpioModeInput, GpioPullNo, GpioSpeedLow);
furi_hal_subghz_flush_rx(); furi_hal_subghz_flush_rx();
furi_hal_subghz_rx(); furi_hal_subghz_rx();
if (!app->txrx->debug_timer_sampling) {
furi_hal_subghz_start_async_rx(subghz_worker_rx_callback, app->txrx->worker); furi_hal_subghz_start_async_rx(subghz_worker_rx_callback,
subghz_worker_start(app->txrx->worker); app->txrx->worker);
subghz_worker_start(app->txrx->worker);
} else {
raw_sampling_worker_start(app);
}
app->txrx->txrx_state = TxRxStateRx; app->txrx->txrx_state = TxRxStateRx;
return value; return value;
} }
@@ -64,9 +76,13 @@ uint32_t radio_rx(ProtoViewApp* app) {
void radio_rx_end(ProtoViewApp* app) { void radio_rx_end(ProtoViewApp* app) {
furi_assert(app); furi_assert(app);
if (app->txrx->txrx_state == TxRxStateRx) { if (app->txrx->txrx_state == TxRxStateRx) {
if(subghz_worker_is_running(app->txrx->worker)) { if (!app->txrx->debug_timer_sampling) {
subghz_worker_stop(app->txrx->worker); if(subghz_worker_is_running(app->txrx->worker)) {
furi_hal_subghz_stop_async_rx(); subghz_worker_stop(app->txrx->worker);
furi_hal_subghz_stop_async_rx();
}
} else {
raw_sampling_worker_stop(app);
} }
} }
furi_hal_subghz_idle(); furi_hal_subghz_idle();
@@ -84,3 +100,55 @@ void radio_sleep(ProtoViewApp* app) {
furi_hal_subghz_sleep(); furi_hal_subghz_sleep();
app->txrx->txrx_state = TxRxStateSleep; app->txrx->txrx_state = TxRxStateSleep;
} }
/* ============================= Raw sampling mode =============================
* This is a special mode that uses a high frequency timer to sample the
* CC1101 pin directly. It's useful for debugging purposes when we want
* to get the raw data from the chip and completely bypass the subghz
* Flipper system.
* ===========================================================================*/
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) {
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;
raw_samples_add(RawSamples, app->txrx->last_g0_value, dur);
app->txrx->last_g0_value = level;
app->txrx->last_g0_change_time = now;
}
LL_TIM_ClearFlag_UPDATE(TIM2);
}
void raw_sampling_worker_start(ProtoViewApp *app) {
UNUSED(app);
LL_TIM_InitTypeDef tim_init = {
.Prescaler = 63, /* CPU frequency is ~64Mhz. */
.CounterMode = LL_TIM_COUNTERMODE_UP,
.Autoreload = 5, /* Sample every 5 us */
};
LL_TIM_Init(TIM2, &tim_init);
LL_TIM_SetClockSource(TIM2, LL_TIM_CLOCKSOURCE_INTERNAL);
LL_TIM_DisableCounter(TIM2);
LL_TIM_SetCounter(TIM2, 0);
furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, protoview_timer_isr, app);
LL_TIM_EnableIT_UPDATE(TIM2);
LL_TIM_EnableCounter(TIM2);
FURI_LOG_E(TAG, "Timer enabled");
}
void raw_sampling_worker_stop(ProtoViewApp *app) {
UNUSED(app);
FURI_CRITICAL_ENTER();
LL_TIM_DisableCounter(TIM2);
LL_TIM_DisableIT_UPDATE(TIM2);
furi_hal_interrupt_set_isr(FuriHalInterruptIdTIM2, NULL, NULL);
LL_TIM_DeInit(TIM2);
FURI_CRITICAL_EXIT();
}

View File

@@ -1,6 +1,6 @@
App( App(
appid="protoview", appid="protoview",
name="Protocols visualizer", name="ProtoView",
apptype=FlipperAppType.EXTERNAL, apptype=FlipperAppType.EXTERNAL,
entry_point="protoview_app_entry", entry_point="protoview_app_entry",
cdefines=["APP_PROTOVIEW"], cdefines=["APP_PROTOVIEW"],

View File

@@ -0,0 +1,4 @@
#!/bin/sh
BINPATH="/Users/antirez/hack/flipper/official/build/f7-firmware-D/.extapps/protoview.fap"
cp $BINPATH .
git commit -a -m 'Binary file updated.'

View File

@@ -0,0 +1,20 @@
#include <stdint.h>
#include <stddef.h>
/* 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 crc = init;
size_t i, j;
for (i = 0; i < len; i++) {
crc ^= data[i];
for (j = 0; j < 8; j++) {
if ((crc & 0x80) != 0)
crc = (uint8_t)((crc << 1) ^ poly);
else
crc <<= 1;
}
}
return crc;
}

View File

@@ -11,7 +11,7 @@
* *
* ((256+MDMCFG3)*(2^MDMCFG4:0..3bits)) / 2^28 * 26000000. * ((256+MDMCFG3)*(2^MDMCFG4:0..3bits)) / 2^28 * 26000000.
* *
* For instance for the default values of MDMCFG3 (34) and MDMCFG4 (12): * For instance for the default values of MDMCFG3[0..3] (34) and MDMCFG4 (12):
* *
* ((256+34)*(2^12))/(2^28)*26000000 = 115051.2688000000, that is 115KBaud * ((256+34)*(2^12))/(2^28)*26000000 = 115051.2688000000, that is 115KBaud
* *
@@ -38,6 +38,23 @@
* d 82 khz * d 82 khz
* e 68 khz * e 68 khz
* f 58 khz * f 58 khz
*
* FSK deviation is controlled by the DEVIATION register. In Ruby:
*
* dev = (26000000.0/2**17)*(8+(deviation&7))*(2**(deviation>>4&7))
*
* deviation&7 (last three bits) is the deviation mantissa, while
* deviation>>4&7 (bits 6,5,4) are the exponent.
*
* Deviations values according to certain configuration of DEVIATION:
*
* 0x04 -> 2.380371 kHz
* 0x24 -> 9.521484 kHz
* 0x34 -> 19.042969 Khz
* 0x40 -> 25.390625 Khz
* 0x43 -> 34.912109 Khz
* 0x45 -> 41.259765 Khz
* 0x47 -> 47.607422 kHz
*/ */
/* 20 KBaud, 2FSK, 28.56 kHz deviation, 325 Khz bandwidth filter. */ /* 20 KBaud, 2FSK, 28.56 kHz deviation, 325 Khz bandwidth filter. */
@@ -130,3 +147,46 @@ static uint8_t protoview_subghz_tpms2_async_regs[][2] = {
{0, 0}, {0, 0},
}; };
/* Parameters that should work well for the TPMS PVM C210 sensor. */
static uint8_t protoview_subghz_tpms3_async_regs[][2] = {
/* GPIO GD0 */
{CC1101_IOCFG0, 0x0D}, // GD0 as async serial data output/input
/* Frequency Synthesizer Control */
{CC1101_FSCTRL1, 0x06}, // IF = (26*10^6) / (2^10) * 0x06 = 152343.75Hz
/* Packet engine */
{CC1101_PKTCTRL0, 0x32}, // Async, continious, no whitening
{CC1101_PKTCTRL1, 0x04},
// // Modem Configuration
{CC1101_MDMCFG0, 0x00},
{CC1101_MDMCFG1, 0x02}, // 2 is the channel spacing exponet: not used
{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
/* Main Radio Control State Machine */
{CC1101_MCSM0, 0x18}, // Autocalibrate on idle-to-rx/tx, PO_TIMEOUT is 64 cycles(149-155us)
/* Frequency Offset Compensation Configuration */
{CC1101_FOCCFG,
0x16}, // no frequency offset compensation, POST_K same as PRE_K, PRE_K is 4K, GATE is off
/* Automatic Gain Control */
{CC1101_AGCCTRL0, 0x80},
{CC1101_AGCCTRL1, 0x58},
{CC1101_AGCCTRL2, 0x87},
/* Wake on radio and timeouts control */
{CC1101_WORCTRL, 0xFB}, // WOR_RES is 2^15 periods (0.91 - 0.94 s) 16.5 - 17.2 hours
/* Frontend configuration */
{CC1101_FREND0, 0x10}, // Adjusts current TX LO buffer
{CC1101_FREND1, 0x56},
/* End */
{0, 0},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

View File

@@ -1,7 +1,7 @@
/* PT/SC remotes. Usually 443.92 Mhz OOK. /* PT/SC remotes. Usually 443.92 Mhz OOK.
* *
* This line code is used in many remotes such as Princeton chips * This line code is used in many remotes such as Princeton chips
* named PT<number>, Silian Microelectronics SC5262 and others. * named PT2262, Silian Microelectronics SC5262 and others.
* Basically every 4 pulsee represent a bit, where 1000 means 0, and * Basically every 4 pulsee represent a bit, where 1000 means 0, and
* 1110 means 1. Usually we can read 24 bits of data. * 1110 means 1. Usually we can read 24 bits of data.
* In this specific implementation we check for a prelude that is * In this specific implementation we check for a prelude that is

View File

@@ -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
};

View File

@@ -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
};

View File

@@ -1,16 +1,16 @@
/* Renault tires TPMS. Usually 443.92 Mhz FSK. /* Renault tires TPMS. Usually 443.92 Mhz FSK.
* *
* Preamble + marshal-encoded bits. 9 Bytes in total if we don't * Preamble + sync + Manchester bits. ~48us short pulse.
* count the preamble. */ * 9 Bytes in total not counting the preamble. */
#include "../app.h" #include "../app.h"
#define USE_TEST_VECTOR 0 #define USE_TEST_VECTOR 0
static const char *test_vector = static const char *test_vector =
"10101010" "10101010" "10101010" "10101001" // Preamble + sync. "...01010101010101010110" // Preamble + sync
/* The following is marshal encoded, so each two characters are /* The following is Marshal encoded, so each two characters are
* actaully one bit. 01 = 1, 10 = 0. */ * actaully one bit. 01 = 0, 10 = 1. */
"010110010110" // Flags. "010110010110" // Flags.
"10011001101010011001" // Pressure, multiply by 0.75 to obtain kpa. "10011001101010011001" // Pressure, multiply by 0.75 to obtain kpa.
// 244 kpa here. // 244 kpa here.
@@ -29,22 +29,23 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
numbits = strlen(test_vector); numbits = strlen(test_vector);
} }
if (numbits < 13*8) return false; if (numbits-12 < 9*8) return false;
const char *sync_pattern = "10101010" "10101010" "10101010" "10101001"; const char *sync_pattern = "01010101010101010110";
uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern); uint64_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern);
if (off == BITMAP_SEEK_NOT_FOUND) return false; if (off == BITMAP_SEEK_NOT_FOUND) return false;
FURI_LOG_E(TAG, "Renault TPMS preamble+sync found"); FURI_LOG_E(TAG, "Renault TPMS preamble+sync found");
off += 32; /* Skip preamble. */ off += 20; /* Skip preamble. */
uint8_t raw[9]; uint8_t raw[9];
uint32_t decoded = uint32_t decoded =
convert_from_line_code(raw,sizeof(raw),bits,numbytes,off, convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
"10","01"); /* Manchester. */ "01","10"); /* Manchester. */
FURI_LOG_E(TAG, "Renault TPMS decoded bits: %lu", decoded); FURI_LOG_E(TAG, "Renault TPMS decoded bits: %lu", decoded);
if (decoded < 8*9) return false; /* Require the full 9 bytes. */ if (decoded < 8*9) return false; /* Require the full 9 bytes. */
if (crc8(raw,8,0,7) != raw[8]) return false; /* Require sane CRC. */
float kpa = 0.75 *((uint32_t)((raw[0]&3)<<8) | raw[1]); float kpa = 0.75 *((uint32_t)((raw[0]&3)<<8) | raw[1]);
int temp = raw[2]-30; int temp = raw[2]-30;
@@ -53,8 +54,10 @@ static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoView
snprintf(info->raw,sizeof(info->raw),"%02X%02X%02X%02X%02X%02X%02X%02X%02X", 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[0],raw[1],raw[2],raw[3],raw[4],raw[5],
raw[6],raw[7],raw[8]); raw[6],raw[7],raw[8]);
snprintf(info->info1,sizeof(info->info1),"Pressure %.2f kpa", (double)kpa); snprintf(info->info1,sizeof(info->info1),"Tire ID %02X%02X%02X",
snprintf(info->info2,sizeof(info->info2),"Temperature %d C", temp); raw[3],raw[4],raw[5]);
snprintf(info->info2,sizeof(info->info2),"Pressure %.2f kpa", (double)kpa);
snprintf(info->info3,sizeof(info->info3),"Temperature %d C", temp);
return true; return true;
} }

View File

@@ -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
};

View File

@@ -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 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
};

View File

@@ -53,6 +53,7 @@ uint32_t search_coherent_signal(RawSamplesBuffer *s, uint32_t idx) {
bool level; bool level;
uint32_t dur; uint32_t dur;
raw_samples_get(s, j, &level, &dur); raw_samples_get(s, j, &level, &dur);
if (dur < minlen || dur > maxlen) break; /* return. */ if (dur < minlen || dur > maxlen) break; /* return. */
/* Let's see if it matches a class we already have or if we /* Let's see if it matches a class we already have or if we
@@ -70,7 +71,7 @@ uint32_t search_coherent_signal(RawSamplesBuffer *s, uint32_t idx) {
/* Is the difference in duration between this signal and /* Is the difference in duration between this signal and
* the class we are inspecting less than a given percentage? * the class we are inspecting less than a given percentage?
* If so, accept this signal. */ * If so, accept this signal. */
if (delta < classavg/8) { /* 100%/8 = 12%. */ if (delta < classavg/5) { /* 100%/5 = 20%. */
/* It is useful to compute the average of the class /* It is useful to compute the average of the class
* we are observing. We know how many samples we got so * we are observing. We know how many samples we got so
* far, so we can recompute the average easily. * far, so we can recompute the average easily.
@@ -130,9 +131,9 @@ void scan_for_signal(ProtoViewApp *app) {
/* Try to seek on data that looks to have a regular high low high low /* Try to seek on data that looks to have a regular high low high low
* pattern. */ * pattern. */
uint32_t minlen = 13; /* Min run of coherent samples. Up to uint32_t minlen = 18; /* Min run of coherent samples. With less
12 samples it's very easy to mistake than a few samples it's very easy to
noise for signal. */ mistake noise for signal. */
ProtoViewMsgInfo *info = malloc(sizeof(ProtoViewMsgInfo)); ProtoViewMsgInfo *info = malloc(sizeof(ProtoViewMsgInfo));
uint32_t i = 0; uint32_t i = 0;
@@ -152,18 +153,25 @@ void scan_for_signal(ProtoViewApp *app) {
the signal in the loop. */ the signal in the loop. */
/* Accept this signal as the new signal if either it's longer /* Accept this signal as the new signal if either it's longer
* than the previous one, or the previous one was unknown and * than the previous undecoded one, or the previous one was
* this is decoded. */ * unknown and this is decoded. */
if (thislen > app->signal_bestlen || if ((thislen > app->signal_bestlen && app->signal_decoded == false)
(app->signal_decoded == false && decoded)) || (app->signal_decoded == false && decoded))
{ {
app->signal_info = *info; app->signal_info = *info;
app->signal_bestlen = thislen; app->signal_bestlen = thislen;
app->signal_decoded = decoded; app->signal_decoded = decoded;
raw_samples_copy(DetectedSamples,copy); raw_samples_copy(DetectedSamples,copy);
raw_samples_center(DetectedSamples,i); raw_samples_center(DetectedSamples,i);
FURI_LOG_E(TAG, "Displayed sample updated (%d samples %lu us)", FURI_LOG_E(TAG, "===> Displayed sample updated (%d samples %lu us)",
(int)thislen, DetectedSamples->short_pulse_dur); (int)thislen, DetectedSamples->short_pulse_dur);
/* Adjust raw view scale if the signal has an high
* data rate. */
if (DetectedSamples->short_pulse_dur < 75)
app->us_scale = 10;
else if (DetectedSamples->short_pulse_dur < 145)
app->us_scale = 30;
} }
} }
i += thislen ? thislen : 1; i += thislen ? thislen : 1;
@@ -317,11 +325,9 @@ uint32_t convert_signal_to_bits(uint8_t *b, uint32_t blen, RawSamplesBuffer *s,
/* This function converts the line code used to the final data representation. /* This function converts the line code used to the final data representation.
* The representation is put inside 'buf', for up to 'buflen' bytes of total * The representation is put inside 'buf', for up to 'buflen' bytes of total
* data. For instance in order to convert manchester I can use "10" and "01" * data. For instance in order to convert manchester you can use "10" and "01"
* as zero and one patterns. It is possible to use "?" inside patterns in * as zero and one patterns. However this function does not handle differential
* order to skip certain bits. For instance certain devices encode data twice, * encodings. See below for convert_from_diff_manchester().
* with each bit encoded in manchester encoding and then in its reversed
* representation. In such a case I could use "10??" and "01??".
* *
* The function returns the number of bits converted. It will stop as soon * The function returns the number of bits converted. It will stop as soon
* as it finds a pattern that does not match zero or one patterns, or when * as it finds a pattern that does not match zero or one patterns, or when
@@ -350,17 +356,46 @@ uint32_t convert_from_line_code(uint8_t *buf, uint64_t buflen, uint8_t *bits, ui
return decoded; return decoded;
} }
/* Convert the differential Manchester code to bits. This is similar to
* convert_from_line_code() but specific for Manchester. The user must
* supply the value of the previous symbol before this stream, since
* 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 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);
previous = b1;
if (decoded/8 == buflen) break; /* No space left on target buffer. */
}
return decoded;
}
/* Supported protocols go here, with the relevant implementation inside /* Supported protocols go here, with the relevant implementation inside
* protocols/<name>.c */ * protocols/<name>.c */
extern ProtoViewDecoder Oregon2Decoder; extern ProtoViewDecoder Oregon2Decoder;
extern ProtoViewDecoder B4B1Decoder; extern ProtoViewDecoder B4B1Decoder;
extern ProtoViewDecoder RenaultTPMSDecoder; extern ProtoViewDecoder RenaultTPMSDecoder;
extern ProtoViewDecoder ToyotaTPMSDecoder;
extern ProtoViewDecoder SchraderTPMSDecoder;
extern ProtoViewDecoder CitroenTPMSDecoder;
extern ProtoViewDecoder FordTPMSDecoder;
ProtoViewDecoder *Decoders[] = { ProtoViewDecoder *Decoders[] = {
&Oregon2Decoder, /* Oregon sensors v2.1 protocol. */ &Oregon2Decoder, /* Oregon sensors v2.1 protocol. */
&B4B1Decoder, /* PT, SC, ... 24 bits remotes. */ &B4B1Decoder, /* PT, SC, ... 24 bits remotes. */
&RenaultTPMSDecoder, /* Renault TPMS. */ &RenaultTPMSDecoder, /* Renault TPMS. */
&ToyotaTPMSDecoder, /* Toyota TPMS. */
&SchraderTPMSDecoder, /* Schrader TPMS. */
&CitroenTPMSDecoder, /* Citroen TPMS. */
&FordTPMSDecoder, /* Ford TPMS. */
NULL NULL
}; };
@@ -378,12 +413,13 @@ bool decode_signal(RawSamplesBuffer *s, uint64_t len, ProtoViewMsgInfo *info) {
uint32_t bitmap_bits_size = 4096*8; uint32_t bitmap_bits_size = 4096*8;
uint32_t bitmap_size = bitmap_bits_size/8; uint32_t bitmap_size = bitmap_bits_size/8;
/* We call the decoders with an offset a few bits before the actual /* We call the decoders with an offset a few samples before the actual
* signal detected and for a len of a few bits after its end. */ * signal detected and for a len of a few bits after its end. */
uint32_t before_after_bits = 2; uint32_t before_samples = 20;
uint32_t after_samples = 100;
uint8_t *bitmap = malloc(bitmap_size); uint8_t *bitmap = malloc(bitmap_size);
uint32_t bits = convert_signal_to_bits(bitmap,bitmap_size,s,-before_after_bits,len+before_after_bits*2,s->short_pulse_dur); uint32_t bits = convert_signal_to_bits(bitmap,bitmap_size,s,-before_samples,len+before_samples+after_samples,s->short_pulse_dur);
if (DEBUG_MSG) { /* Useful for debugging purposes. Don't remove. */ if (DEBUG_MSG) { /* Useful for debugging purposes. Don't remove. */
char *str = malloc(1024); char *str = malloc(1024);
@@ -413,7 +449,7 @@ bool decode_signal(RawSamplesBuffer *s, uint64_t len, ProtoViewMsgInfo *info) {
if (!decoded) { if (!decoded) {
FURI_LOG_E(TAG, "No decoding possible"); FURI_LOG_E(TAG, "No decoding possible");
} else { } else {
FURI_LOG_E(TAG, "Decoded %s, raw=%s info=[%s,%s,%s]", info->name, info->raw, info->info1, info->info2, info->info3); 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); free(bitmap);
return decoded; return decoded;

View File

@@ -8,7 +8,18 @@
/* Read directly from the G0 CC1101 pin, and draw a black or white /* Read directly from the G0 CC1101 pin, and draw a black or white
* dot depending on the level. */ * dot depending on the level. */
void render_view_direct_sampling(Canvas *const canvas, ProtoViewApp *app) { void render_view_direct_sampling(Canvas *const canvas, ProtoViewApp *app) {
UNUSED(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_set_font(canvas, FontPrimary);
canvas_draw_str(canvas,14,60,"To enable press OK");
return;
}
for (int y = 0; y < 64; y++) { for (int y = 0; y < 64; y++) {
for (int x = 0; x < 128; x++) { for (int x = 0; x < 128; x++) {
bool level = furi_hal_gpio_read(&gpio_cc1101_g0); bool level = furi_hal_gpio_read(&gpio_cc1101_g0);
@@ -16,31 +27,41 @@ void render_view_direct_sampling(Canvas *const canvas, ProtoViewApp *app) {
/* Busy loop: this is a terrible approach as it blocks /* Busy loop: this is a terrible approach as it blocks
* everything else, but for now it's the best we can do * everything else, but for now it's the best we can do
* to obtain direct data with some spacing. */ * to obtain direct data with some spacing. */
uint32_t x = 500; while(x--); uint32_t x = 250; while(x--);
} }
} }
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
canvas_draw_str_with_border(canvas,40,60,"Direct sampling", canvas_draw_str_with_border(canvas,36,60,"Direct sampling",
ColorWhite,ColorBlack); ColorWhite,ColorBlack);
} }
/* Handle input */ /* Handle input */
void process_input_direct_sampling(ProtoViewApp *app, InputEvent input) { void process_input_direct_sampling(ProtoViewApp *app, InputEvent input) {
UNUSED(app); if (input.type == InputTypePress && input.key == InputKeyOk) {
UNUSED(input); app->direct_sampling_enabled = !app->direct_sampling_enabled;
}
} }
/* Enter view. Stop the subghz thread to prevent access as we read /* Enter view. Stop the subghz thread to prevent access as we read
* the CC1101 data directly. */ * the CC1101 data directly. */
void view_enter_direct_sampling(ProtoViewApp *app) { void view_enter_direct_sampling(ProtoViewApp *app) {
if (app->txrx->txrx_state == TxRxStateRx) { if (app->txrx->txrx_state == TxRxStateRx &&
!app->txrx->debug_timer_sampling)
{
subghz_worker_stop(app->txrx->worker); subghz_worker_stop(app->txrx->worker);
} else {
raw_sampling_worker_stop(app);
} }
} }
/* Exit view. Restore the subghz thread. */ /* Exit view. Restore the subghz thread. */
void view_exit_direct_sampling(ProtoViewApp *app) { void view_exit_direct_sampling(ProtoViewApp *app) {
if (app->txrx->txrx_state == TxRxStateRx) { if (app->txrx->txrx_state == TxRxStateRx &&
!app->txrx->debug_timer_sampling)
{
subghz_worker_start(app->txrx->worker); subghz_worker_start(app->txrx->worker);
} else {
raw_sampling_worker_start(app);
} }
app->direct_sampling_enabled = false;
} }

View File

@@ -25,17 +25,18 @@ void render_view_info(Canvas *const canvas, ProtoViewApp *app) {
canvas_draw_str(canvas, 0, y, buf); canvas_draw_str(canvas, 0, y, buf);
y += lineheight; y += lineheight;
} }
canvas_draw_str(canvas, 0, y, app->signal_info.info1); canvas_draw_str(canvas, 0, y, app->signal_info.info1); y += lineheight;
y += lineheight; canvas_draw_str(canvas, 0, y, app->signal_info.info2); y += lineheight;
canvas_draw_str(canvas, 0, y, app->signal_info.info2); canvas_draw_str(canvas, 0, y, app->signal_info.info3); y += lineheight;
y += lineheight; canvas_draw_str(canvas, 0, y, app->signal_info.info4); y += lineheight;
canvas_draw_str(canvas, 0, y, app->signal_info.info3);
y += lineheight;
} }
/* Handle input for the settings view. */ /* Handle input for the info view. */
void process_input_info(ProtoViewApp *app, InputEvent input) { void process_input_info(ProtoViewApp *app, InputEvent input) {
UNUSED(app); if (input.type == InputTypeShort) {
UNUSED(input); if (input.key == InputKeyOk) {
return; /* Reset the current sample to capture the next. */
reset_current_signal(app);
}
}
} }

View File

@@ -20,6 +20,9 @@ void render_view_settings(Canvas *const canvas, ProtoViewApp *app) {
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas,10,61,"Use up and down to modify"); canvas_draw_str(canvas,10,61,"Use up and down to modify");
if (app->txrx->debug_timer_sampling)
canvas_draw_str(canvas,3,52,"(DEBUG timer sampling is ON)");
/* Show frequency. We can use big numbers font since it's just a number. */ /* Show frequency. We can use big numbers font since it's just a number. */
if (app->current_view == ViewFrequencySettings) { if (app->current_view == ViewFrequencySettings) {
char buf[16]; char buf[16];
@@ -40,6 +43,18 @@ void process_input_settings(ProtoViewApp *app, InputEvent input) {
* modulation. */ * modulation. */
app->frequency = subghz_setting_get_default_frequency(app->setting); app->frequency = subghz_setting_get_default_frequency(app->setting);
app->modulation = 0; app->modulation = 0;
} else if (0 && input.type == InputTypeLong && input.key == InputKeyDown) {
/* 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). */
/* We have to stop the previous sampling system. */
radio_rx_end(app);
/* Then switch mode and start the new one. */
app->txrx->debug_timer_sampling = !app->txrx->debug_timer_sampling;
radio_begin(app);
radio_rx(app);
} else if (input.type == InputTypePress && } else if (input.type == InputTypePress &&
(input.key != InputKeyDown || input.key != InputKeyUp)) (input.key != InputKeyDown || input.key != InputKeyUp))
{ {
@@ -85,9 +100,18 @@ void process_input_settings(ProtoViewApp *app, InputEvent input) {
return; return;
} }
/* Apply changes. */ /* Apply changes when switching to other views. */
FURI_LOG_E(TAG, "Setting view, setting frequency/modulation to %lu %s", app->frequency, ProtoViewModulations[app->modulation].name); app->txrx->freq_mod_changed = true;
radio_rx_end(app); }
radio_begin(app);
radio_rx(app); /* 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);
radio_rx_end(app);
radio_begin(app);
radio_rx(app);
app->txrx->freq_mod_changed = false;
}
} }