forked from flipperdevices/flipperzero-firmware
-
-
Notifications
You must be signed in to change notification settings - Fork 545
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial work for a generic protocol decoder.
- Loading branch information
Showing
3 changed files
with
351 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,314 @@ | ||
#include "../app.h" | ||
|
||
/* Copyright (C) 2023 Salvatore Sanfilippo -- All Rights Reserved | ||
* See the LICENSE file for information about the license. | ||
* | ||
* ---------------------------------------------------------------------------- | ||
* The "unknown" decoder fires as the last one, once we are sure no other | ||
* decoder was able to identify the signal. The goal is to detect the | ||
* preamble and line code used in the received signal, then turn the | ||
* decoded bits into bytes. | ||
* | ||
* The techniques used for the detection are described in the comments | ||
* below. | ||
* ---------------------------------------------------------------------------- | ||
*/ | ||
|
||
|
||
/* Scan the signal bitmap looking for a PWM modulation. In this case | ||
* for PWM we are referring to two exact patterns of high and low | ||
* signal (each bit in the bitmap is worth the smallest gap/pulse duration | ||
* we detected) that repeat each other in a given segment of the message. | ||
* | ||
* This modulation is quite common, for instance sometimes zero and | ||
* one are rappresented by a 700us pulse followed by 350 gap, | ||
* and 350us pulse followed by a 700us gap. So the signal bitmap received | ||
* by the decoder would contain 110 and 100 symbols. | ||
* | ||
* The way this function work is commented inline. | ||
* | ||
* The function returns the number of consecutive symbols found, having | ||
* a symbol length of 'symlen' (3 in the above example), and stores | ||
* in *s1i the offset of the first symbol found, and in *s2i the offset | ||
* of the second symbol. The function can't tell which is one and which | ||
* zero. */ | ||
static uint32_t find_pwm(uint8_t *bits, uint32_t numbytes, uint32_t numbits, | ||
uint32_t symlen, uint32_t *s1i, uint32_t *s2i) | ||
{ | ||
uint32_t best_count = 0; /* Max number of symbols found in this try. */ | ||
uint32_t best_idx1 = 0; /* First symbol offset of longest sequence found. | ||
* This is also the start sequence offset. */ | ||
uint32_t best_idx2 = 0; /* Second symbol offset. */ | ||
|
||
/* Try all the possible symbol offsets that are less of our | ||
* symbol len. This is likely not really useful but we take | ||
* a conservative approach. Because if have have, for instance, | ||
* repeating symbols "100" and "110", they will form a sequence | ||
* that is choerent at different offsets, but out-of-sync. | ||
* | ||
* Anyway at the end of the function we try to fix the sync. */ | ||
for (uint32_t off = 0; off < symlen; off++) { | ||
uint32_t c = 0; // Number of contiguous symbols found. | ||
*s1i = off; // Assume we start at one symbol boundaty. | ||
*s2i = UINT32_MAX; // Second symbol first index still unknown. | ||
uint32_t next = off; | ||
|
||
/* We scan the whole bitmap in one pass, resetting the state | ||
* each time we find a pattern that is not one of the two | ||
* symbols we found so far. */ | ||
while(next < numbits-symlen) { | ||
bool match1 = bitmap_match_bitmap(bits,numbytes,next, | ||
bits,numbytes,*s1i, | ||
symlen); | ||
if (!match1 && *s2i == UINT32_MAX) { | ||
/* It's not the first sybol. We don't know how the | ||
* second look like. Assume we found an occurrence of | ||
* the second symbol. */ | ||
*s2i = next; | ||
} | ||
|
||
bool match2 = bitmap_match_bitmap(bits,numbytes,next, | ||
bits,numbytes,*s2i, | ||
symlen); | ||
|
||
/* One or the other should match. */ | ||
if (match1 || match2) { | ||
c++; | ||
if (c > best_count) { | ||
best_count = c; | ||
best_idx1 = *s1i; | ||
best_idx2 = *s2i; | ||
} | ||
next += symlen; | ||
} else { | ||
/* No match. Continue resetting the signal info. */ | ||
c = 0; // Start again to count contiguous symbols. | ||
*s1i = next; // First symbol always at start. | ||
*s2i = UINT32_MAX; // Second symbol unknown. | ||
} | ||
} | ||
} | ||
|
||
/* We don't know if we are really synchronized with the bits at this point. | ||
* For example if zero bit is 100 and one bit is 110 in a specific | ||
* line code, our detector could randomly believe it's 001 and 101. | ||
* However PWD line codes normally start with a pulse in both symbols. | ||
* If that is the case, let's align. */ | ||
uint32_t shift; | ||
for (shift = 0; shift < symlen; shift++) { | ||
if (bitmap_get(bits,numbytes,best_idx1+shift) && | ||
bitmap_get(bits,numbytes,best_idx2+shift)) break; | ||
} | ||
if (shift != symlen) { | ||
best_idx1 += shift; | ||
best_idx2 += shift; | ||
} | ||
|
||
*s1i = best_idx1; | ||
*s2i = best_idx2; | ||
return best_count; | ||
} | ||
|
||
/* Find the longest sequence that looks like Manchester coding. | ||
* | ||
* Manchester coding requires each pairs of bits to be either | ||
* 01 or 10. We'll have to try odd and even offsets to be | ||
* sure to find it. | ||
* | ||
* Note that this will also detect differential Manchester, but | ||
* will report it as Manchester. I can't think of any way to | ||
* distinguish between the two line codes, because shifting them | ||
* one symbol will make one to look like the other. | ||
* | ||
* Only option could be to decode the message with both line | ||
* codes and use statistical properties (common byte values) | ||
* to determine what's more likely, but this looks very fragile. | ||
* | ||
* Fortunately differential Manchester is more rarely used, | ||
* so we can assume Manchester most of the times. Yet we are left | ||
* with the indetermination about zero being pulse-gap or gap-pulse | ||
* or the other way around. | ||
* | ||
* If the 'only_raising' parameter is true, the function detects | ||
* only sequences going from gap to pulse: this is useful in order | ||
* to locate preambles of alternating gaps and pulses. */ | ||
static uint32_t find_alternating_bits(uint8_t *bits, uint32_t numbytes, | ||
uint32_t numbits, uint32_t *start, bool only_raising) | ||
{ | ||
uint32_t best_count = 0; // Max number of symbols found | ||
uint32_t best_off = 0; // Max symbols start offset. | ||
for (int odd = 0; odd < 2; odd++) { | ||
uint32_t count = 0; // Symbols found so far | ||
uint32_t start_off = odd; | ||
uint32_t j = odd; | ||
while (j < numbits-1) { | ||
bool bit1 = bitmap_get(bits,numbytes,j); | ||
bool bit2 = bitmap_get(bits,numbytes,j+1); | ||
if ((!only_raising && bit1 != bit2) || | ||
(only_raising && !bit1 && bit2)) | ||
{ | ||
count++; | ||
if (count > best_count) { | ||
best_count = count; | ||
best_off = start_off; | ||
} | ||
} else { | ||
/* End of sequence. Continue with the next | ||
* part of the signal. */ | ||
count = 0; | ||
start_off = j + 2; | ||
} | ||
j += 2; | ||
} | ||
} | ||
*start = best_off; | ||
return best_count; | ||
} | ||
|
||
/* Wrapper to find Manchester code. */ | ||
static uint32_t find_manchester(uint8_t *bits, uint32_t numbytes, | ||
uint32_t numbits, uint32_t *start) | ||
{ | ||
return find_alternating_bits(bits,numbytes,numbits,start,false); | ||
} | ||
|
||
/* Wrapper to find preamble sections. */ | ||
static uint32_t find_preamble(uint8_t *bits, uint32_t numbytes, | ||
uint32_t numbits, uint32_t *start) | ||
{ | ||
return find_alternating_bits(bits,numbytes,numbits,start,true); | ||
} | ||
|
||
typedef enum { | ||
LineCodeNone, | ||
LineCodeManchester, | ||
LineCodePWM3, | ||
LineCodePWM4, | ||
} LineCodeGuess; | ||
|
||
static char *get_linecode_name(LineCodeGuess lc) { | ||
switch(lc) { | ||
case LineCodeNone: return "none"; | ||
case LineCodeManchester: return "Manchester"; | ||
case LineCodePWM3: return "PWM3"; | ||
case LineCodePWM4: return "PWM4"; | ||
} | ||
return "unknown"; | ||
} | ||
|
||
static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) { | ||
|
||
/* No decoder was able to detect this message. Let's try if we can | ||
* find some structure. To start, we'll see if it looks like is | ||
* manchester coded, or PWM with symbol len of 3 or 4. */ | ||
|
||
/* For PWM, start1 and start2 are the offsets at which the two | ||
* sequences composing the message appear the first time. | ||
* So start1 is also the message start offset. Start2 is not used | ||
* for Manchester, that does not have two separated symbols like | ||
* PWM. */ | ||
uint32_t start1 = 0, start2 = 0; | ||
uint32_t msgbits; // Number of message bits in the bitmap, so | ||
// this will be the number of symbols, not actual | ||
// bits after the message is decoded. | ||
uint32_t tmp1, tmp2; // Temp vars to store the start. | ||
uint32_t minbits = 16; // Less than that gets undetected. | ||
uint32_t pwm_len; // Bits per symbol, in the case of PWM. | ||
LineCodeGuess linecode = LineCodeNone; | ||
|
||
// Try PWM3 | ||
uint32_t pwm3_bits = find_pwm(bits,numbytes,numbits,3,&tmp1,&tmp2); | ||
if (pwm3_bits >= minbits) { | ||
linecode = LineCodePWM3; | ||
start1 = tmp1; | ||
start2 = tmp2; | ||
pwm_len = 3; | ||
msgbits = pwm3_bits*pwm_len; | ||
} | ||
|
||
// Try PWM4 | ||
uint32_t pwm4_bits = find_pwm(bits,numbytes,numbits,4,&tmp1,&tmp2); | ||
if (pwm4_bits >= minbits && pwm4_bits > pwm3_bits) { | ||
linecode = LineCodePWM4; | ||
start1 = tmp1; | ||
start2 = tmp2; | ||
pwm_len = 4; | ||
msgbits = pwm3_bits*pwm_len; | ||
} | ||
|
||
// Try Manchester | ||
uint32_t manchester_bits = find_manchester(bits,numbytes,numbits,&tmp1); | ||
if (manchester_bits > minbits && | ||
manchester_bits > pwm3_bits && | ||
manchester_bits > pwm4_bits) | ||
{ | ||
linecode = LineCodeManchester; | ||
start1 = tmp1; | ||
msgbits = pwm3_bits*2; | ||
} | ||
|
||
if (linecode == LineCodeNone) return false; | ||
|
||
/* Often there is a preamble before the signal. We'll try to find | ||
* it, and if it is not too far away from our signal, we'll claim | ||
* our signal starts at the preamble. */ | ||
uint32_t preamble_len = find_preamble(bits,numbytes,numbits,&tmp1); | ||
uint32_t min_preamble_len = 10; | ||
uint32_t max_preamble_distance = 32; | ||
uint32_t preamble_start; | ||
bool preamble_found = false; | ||
|
||
if (preamble_len >= min_preamble_len && // Not too short. | ||
tmp1 < start1 && // Should be before the data. | ||
start1-tmp1 <= max_preamble_distance) // Not too far. | ||
{ | ||
preamble_start = tmp1; | ||
preamble_found = true; | ||
} | ||
|
||
info->start_off = preamble_found ? preamble_start : start1; | ||
info->pulses_count = (start1+msgbits) - info->start_off; | ||
info->pulses_count += 20; /* Add a few more, so that if the user resends | ||
* the message, it is more likely we will | ||
* transfer all that is needed, like a message | ||
* terminator (that we don't detect). */ | ||
|
||
/* We think there is a message and we know where it starts and the | ||
* line code used. We can turn it into bits and bytes. */ | ||
uint32_t decoded; | ||
uint8_t data[32]; | ||
uint32_t datalen; | ||
|
||
char symbol1[5], symbol2[5]; | ||
if (linecode == LineCodePWM3 || linecode == LineCodePWM4) { | ||
bitmap_to_string(symbol1,bits,numbytes,start1,pwm_len); | ||
bitmap_to_string(symbol2,bits,numbytes,start2,pwm_len); | ||
} else if (linecode == LineCodeManchester) { | ||
memcpy(symbol1,"01",3); | ||
memcpy(symbol2,"10",3); | ||
} | ||
|
||
decoded = convert_from_line_code(data,sizeof(data),bits,numbytes,start1, | ||
symbol1,symbol2); | ||
datalen = (decoded+7)/8; | ||
|
||
char *linecode_name = get_linecode_name(linecode); | ||
fieldset_add_str(info->fieldset,"line code",linecode_name,strlen(linecode_name)); | ||
fieldset_add_uint(info->fieldset,"preamble len",preamble_len,8); | ||
fieldset_add_str(info->fieldset,"first symbol",symbol1,strlen(symbol1)); | ||
fieldset_add_str(info->fieldset,"second symbol",symbol2,strlen(symbol2)); | ||
fieldset_add_uint(info->fieldset,"data bits",decoded,8); | ||
for (uint32_t j = 0; j < datalen; j++) { | ||
char label[16]; | ||
snprintf(label,sizeof(label),"data[%lu]",j); | ||
fieldset_add_bytes(info->fieldset,label,data+j,2); | ||
} | ||
return true; | ||
} | ||
|
||
ProtoViewDecoder UnknownDecoder = { | ||
.name = "Unknown", | ||
.decode = decode, | ||
.get_fields = NULL, | ||
.build_message = NULL | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters