/* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved * See the LICENSE file for information about the license. */ #include "app.h" bool decode_signal(RawSamplesBuffer* s, uint64_t len, ProtoViewMsgInfo* info); /* ============================================================================= * Protocols table. * * Supported protocols go here, with the relevant implementation inside * protocols/.c * ===========================================================================*/ extern ProtoViewDecoder Oregon2Decoder; extern ProtoViewDecoder B4B1Decoder; extern ProtoViewDecoder RenaultTPMSDecoder; extern ProtoViewDecoder ToyotaTPMSDecoder; extern ProtoViewDecoder SchraderTPMSDecoder; extern ProtoViewDecoder SchraderEG53MA4TPMSDecoder; extern ProtoViewDecoder CitroenTPMSDecoder; extern ProtoViewDecoder FordTPMSDecoder; extern ProtoViewDecoder KeeloqDecoder; extern ProtoViewDecoder ProtoViewChatDecoder; extern ProtoViewDecoder UnknownDecoder; 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. */ &ProtoViewChatDecoder, /* Protoview simple text messages. */ /* Warning: the following decoder must stay at the end of the * list. Otherwise would detect most signals and prevent the actaul * decoders from handling them. */ &UnknownDecoder, /* General protocol detector. */ NULL}; /* ============================================================================= * Raw signal detection * ===========================================================================*/ /* Return the time difference between a and b, always >= 0 since * the absolute value is returned. */ uint32_t duration_delta(uint32_t a, uint32_t b) { return a > b ? a - b : b - a; } /* Reset the current signal, so that a new one can be detected. */ void reset_current_signal(ProtoViewApp* app) { app->signal_bestlen = 0; app->signal_offset = 0; app->signal_decoded = false; raw_samples_reset(DetectedSamples); raw_samples_reset(RawSamples); free_msg_info(app->msg_info); app->msg_info = NULL; } /* This function starts scanning samples at offset idx looking for the * longest run of pulses, either high or low, that are not much different * from each other, for a maximum of three duration classes. * So for instance 50 successive pulses that are roughly long 340us or 670us * will be sensed as a coherent signal (example: 312, 361, 700, 334, 667, ...) * * The classes are counted separtely for high and low signals (RF on / off) * because many devices tend to have different pulse lenghts depending on * the level of the pulse. * * 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 min_duration) { struct { 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)); // Set a min/max duration limit for samples to be considered part of a // coherent signal. The maximum length is fixed while the minimum // is passed as argument, as depends on the data rate and in general // on the signal to analyze. uint32_t max_duration = 4000; uint32_t len = 0; /* Observed len of coherent samples. */ s->short_pulse_dur = 0; for(uint32_t j = idx; j < idx + s->total; j++) { bool level; uint32_t dur; raw_samples_get(s, j, &level, &dur); if(dur < min_duration || dur > max_duration) 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) { 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); /* 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%. */ /* 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); classes[k].dur[level] = classavg; classes[k].count[level]++; break; /* Sample accepted. */ } } } if(k == SEARCH_CLASSES) break; /* No match, return. */ /* If we are here, we accepted this sample. Try with the next * one. */ len++; } /* 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]) { short_dur[level] = classes[j].dur[level]; } } } /* 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; return len; } /* Called when we detect a message. Just blinks when the message was * not decoded. Vibrates, too, when the message was correctly decoded. */ void notify_signal_detected(ProtoViewApp* app, bool decoded) { static const NotificationSequence decoded_seq = { &message_vibro_on, &message_green_255, &message_delay_50, &message_green_0, &message_vibro_off, NULL}; static const NotificationSequence unknown_seq = { &message_red_255, &message_delay_50, &message_red_0, NULL}; if(decoded) notification_message(app->notification, &decoded_seq); else notification_message(app->notification, &unknown_seq); } /* Search the source buffer with the stored signal (last N samples received) * 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, RawSamplesBuffer* source, uint32_t min_duration) { /* We need to work on a copy: the source buffer may be populated * by the background thread receiving data. */ RawSamplesBuffer* copy = raw_samples_alloc(); raw_samples_copy(copy, source); /* 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 than a few samples it's very easy to mistake noise for signal. */ uint32_t i = 0; while(i < copy->total - 1) { uint32_t thislen = search_coherent_signal(copy, i, min_duration); /* For messages that are long enough, attempt decoding. */ if(thislen > minlen) { /* Allocate the message information that some decoder may * fill, in case it is able to decode a message. */ ProtoViewMsgInfo* info = malloc(sizeof(ProtoViewMsgInfo)); init_msg_info(info, app); info->short_pulse_dur = copy->short_pulse_dur; uint32_t saved_idx = copy->idx; /* Save index, see later. */ /* decode_signal() expects the detected signal to start * from index zero .*/ 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. */ bool oldsignal_not_decoded = app->signal_decoded == false || app->msg_info->decoder == &UnknownDecoder; if(oldsignal_not_decoded && (thislen > app->signal_bestlen || (decoded && info->decoder != &UnknownDecoder))) { free_msg_info(app->msg_info); app->msg_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); adjust_raw_view_scale(app, DetectedSamples->short_pulse_dur); if(app->msg_info->decoder != &UnknownDecoder) notify_signal_detected(app, decoded); } else { /* If the structure was not filled, discard it. Otherwise * now the owner is app->msg_info. */ free_msg_info(info); } } i += thislen ? thislen : 1; } raw_samples_free(copy); } /* ============================================================================= * Decoding * * The following code will translates the raw singals as received by * the CC1101 into logical signals: a bitmap of 0s and 1s sampled at * the detected data clock interval. * * Then the converted signal is passed to the protocols decoders, that look * for protocol-specific information. We stop at the first decoder that is * able to decode the data, so protocols here should be registered in * order of complexity and specificity, with the generic ones at the end. * ===========================================================================*/ /* 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; else 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; } /* Copy 'count' bits from the bitmap 's' of 'slen' total bytes, to the * bitmap 'd' of 'dlen' total bytes. The bits are copied starting from * offset 'soff' of the source bitmap to the offset 'doff' of the * destination bitmap. */ void bitmap_copy( uint8_t* d, uint32_t dlen, uint32_t doff, uint8_t* s, uint32_t slen, uint32_t soff, uint32_t count) { /* If we are byte-aligned in both source and destination, use a fast * path for the number of bytes we can consume this way. */ if((doff & 7) == 0 && (soff & 7) == 0) { uint32_t didx = doff / 8; uint32_t sidx = soff / 8; while(count > 8 && didx < dlen && sidx < slen) { d[didx++] = s[sidx++]; count -= 8; } doff = didx * 8; soff = sidx * 8; /* Note that if we entered this path, the count at the end * of the loop will be < 8. */ } /* Copy the bits needed to reach an offset where we can copy * two half bytes of src to a full byte of destination. */ while(count > 8 && (doff & 7) != 0) { bool bit = bitmap_get(s, slen, soff++); bitmap_set(d, dlen, doff++, bit); count--; } /* If we are here and count > 8, we have an offset that is byte aligned * to the destination bitmap, but not aligned to the source bitmap. * We can copy fast enough by shifting each two bytes of the original * bitmap. * * This is how it works: * * dst: * +--------+--------+--------+ * | 0 | 1 | 2 | * | | | | <- data to fill * +--------+--------+--------+ * ^ * | * doff = 8 * * src: * +--------+--------+--------+ * | 0 | 1 | 2 | * |hellowor|ld!HELLO|WORLDS!!| <- data to copy * +--------+--------+--------+ * ^ * | * soff = 11 * * skew = 11%8 = 3 * each destination byte in dst will receive: * * dst[doff/8] = (src[soff/8] << skew) | (src[soff/8+1] >> (8-skew)) * * dstbyte = doff/8 = 8/8 = 1 * srcbyte = soff/8 = 11/8 = 1 * * so dst[1] will get: * src[1] << 3, that is "ld!HELLO" << 3 = "HELLO..." * xored with * src[2] << 5, that is "WORLDS!!" >> 5 = ".....WOR" * That is "HELLOWOR" */ if(count > 8) { uint8_t skew = soff % 8; /* Don't worry, compiler will optimize. */ uint32_t didx = doff / 8; uint32_t sidx = soff / 8; while(count > 8 && didx < dlen && sidx < slen) { d[didx] = ((s[sidx] << skew) | (s[sidx + 1] >> (8 - skew))); sidx++; didx++; soff += 8; doff += 8; count -= 8; } } /* Here count is guaranteed to be < 8. * Copy the final bits bit by bit. */ while(count) { bool bit = bitmap_get(s, slen, soff++); bitmap_set(d, dlen, doff++, bit); count--; } } /* 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_reverse_bytes_bits(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 expected = (bits[j] == '1') ? true : false; if(bitmap_get(b, blen, bitpos + j) != expected) return false; } return true; } /* Search for the specified bit sequence (see bitmap_match_bits() for details) * in the bitmap 'b' of 'blen' bytes, looking forward at most 'maxbits' ahead. * Returns the offset (in bits) of the match, or BITMAP_SEEK_NOT_FOUND if not * found. * * 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; return BITMAP_SEEK_NOT_FOUND; } /* Compare bitmaps b1 and b2 (possibly overlapping or the same bitmap), * at the specified offsets, for cmplen bits. Returns true if the * exact same bits are found, otherwise false. */ bool bitmap_match_bitmap( uint8_t* b1, uint32_t b1len, uint32_t b1off, uint8_t* b2, uint32_t b2len, uint32_t b2off, uint32_t cmplen) { for(uint32_t j = 0; j < cmplen; j++) { bool bit1 = bitmap_get(b1, b1len, b1off + j); bool bit2 = bitmap_get(b2, b2len, b2off + j); if(bit1 != bit2) return false; } return true; } /* Convert 'len' bitmap bits of the bitmap 'bitmap' into a null terminated * string, stored at 'dst', that must have space at least for len+1 bytes. * The bits are extracted from the specified offset. */ void bitmap_to_string(char* dst, uint8_t* b, uint32_t blen, uint32_t off, uint32_t len) { for(uint32_t j = 0; j < len; j++) dst[j] = bitmap_get(b, blen, off + j) ? '1' : '0'; dst[len] = 0; } /* Set the pattern 'pat' into the bitmap 'b' of max length 'blen' bytes, * starting from the specified offset. * * The pattern is given as a string of 0s and 1s characters, like "01101001". * 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, uint32_t off, const char* pat) { uint32_t i = 0; while(pat[i]) { bitmap_set(b, blen, i + off, pat[i] == '1'); i++; } } /* Take the raw signal and turn it into a sequence of bits inside the * buffer 'b'. Note that such 0s and 1s are NOT the actual data in the * signal, but is just a low level representation of the line code. Basically * if the short pulse we find in the signal is 320us, we convert high and * low levels in the raw sample in this way: * * If for instance we see a high level lasting ~600 us, we will add * two 1s bit. If then the signal goes down for 330us, we will add one zero, * and so forth. So for each period of high and low we find the closest * multiple and set the relevant number of bits. * * In case of a short pulse of 320us detected, 320*2 is the closest to a * high pulse of 600us, so 2 bits will be set. * * In other terms what this function does is sampling the signal at * fixed 'rate' intervals. * * This representation makes it simple to decode the signal at a higher * level later, translating it from Marshal coding or other line codes * to the actual bits/bytes. * * The 'idx' argument marks the detected signal start index into the * raw samples buffer. The 'count' tells the function how many raw * samples to convert into bits. The function returns the number of * 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 bitpos = 0; for(uint32_t j = 0; j < count; j++) { uint32_t dur; bool level; 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. */ /* 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(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; while(numbits--) bitmap_set(b, blen, bitpos++, level); } return bitpos; } /* 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 * data. For instance in order to convert manchester you can use "10" and "01" * as zero and one patterns. However this function does not handle differential * encodings. See below for convert_from_diff_manchester(). * * 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 * the end of the bitmap pointed by 'bits' is reached (the length is * 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 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)) { bitval = false; off += strlen(zero_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. */ } return decoded; } /* Convert the differential Manchester code to bits. This is similar to * convert_from_line_code() but specific for diff-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; } /* Free the message info and allocated data. */ void free_msg_info(ProtoViewMsgInfo* i) { if(i == NULL) return; fieldset_free(i->fieldset); free(i->bits); free(i); } /* Reset the message info structure before passing it to the decoding * functions. */ void init_msg_info(ProtoViewMsgInfo* i, ProtoViewApp* app) { UNUSED(app); memset(i, 0, sizeof(ProtoViewMsgInfo)); i->bits = NULL; i->fieldset = fieldset_new(); } /* 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; /* 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 = 32; 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); 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'; } str[j] = 0; FURI_LOG_E(TAG, "%lu bits sampled: %s", bits, str); free(str); } /* Try all the decoders available. */ int j = 0; bool decoded = false; while(Decoders[j]) { uint32_t start_time = furi_get_tick(); 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) { info->decoder = Decoders[j]; break; } j++; } if(!decoded) { FURI_LOG_E(TAG, "No decoding possible"); } else { FURI_LOG_E(TAG, "+++ Decoded %s", info->decoder->name); /* The message was correctly decoded: fill the info structure * with the decoded signal. The decoder may not implement offset/len * filling of the structure. In such case we have no info and * pulses_count will be set to zero. */ if(info->pulses_count) { info->bits_bytes = (info->pulses_count + 7) / 8; // Round to full byte. info->bits = malloc(info->bits_bytes); bitmap_copy( info->bits, info->bits_bytes, 0, bitmap, bitmap_size, info->start_off, info->pulses_count); } } free(bitmap); return decoded; }