This commit is contained in:
2026-04-07 15:08:02 +03:00
parent 7651f07e0a
commit c29fe2cf7c
3 changed files with 129 additions and 81 deletions

View File

@ -3,9 +3,11 @@
#include "IrFoxDecoder.h"
#include <AnalyzerChannelData.h>
#include <AnalyzerResults.h>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
IrFoxAnalyzer::IrFoxAnalyzer()
: Analyzer2(),
@ -72,6 +74,45 @@ void IrFoxAnalyzer::WorkerThread()
IrFoxDecoder decoder;
decoder.reset();
/** Потоковый фильтр: убирает импульсы короче kMinFilteredPulseUs (иголки/дребезг в сэмплах). */
const U64 min_seg_samples =
std::max<U64>(1ULL, static_cast<U64>((static_cast<double>(irfox::kMinFilteredPulseUs) * 1e-6) * static_cast<double>(fs) + 0.5));
struct RawEdge
{
U64 sample;
bool rising;
};
std::vector<RawEdge> pending;
U64 last_dec_edge_sample = 0;
bool last_dec_edge_valid = false;
auto collapse_short_pairs = [&]() {
for (size_t i = 0; i + 1 < pending.size();)
{
if (pending[i + 1].sample - pending[i].sample < min_seg_samples)
{
pending.erase(pending.begin() + static_cast<std::ptrdiff_t>(i),
pending.begin() + static_cast<std::ptrdiff_t>(i + 2));
if (i > 0)
--i;
}
else
++i;
}
};
auto strip_vs_last_decoder = [&]() {
for (;;)
{
collapse_short_pairs();
if (!last_dec_edge_valid || pending.empty())
return;
if (pending[0].sample - last_dec_edge_sample >= min_seg_samples)
return;
pending.erase(pending.begin());
}
};
U32 frames_since_commit = 0;
const U32 kCommitBatch = 256;
@ -130,6 +171,43 @@ void IrFoxAnalyzer::WorkerThread()
}
};
auto emit_confirmed_edges = [&]() {
for (;;)
{
collapse_short_pairs();
strip_vs_last_decoder();
if (pending.size() < 2)
return;
if (pending[1].sample - pending[0].sample < min_seg_samples)
continue;
decoder.processEdge(pending[0].sample, pending[0].rising, fs, on_bit, on_pkt);
last_dec_edge_sample = pending[0].sample;
last_dec_edge_valid = true;
pending.erase(pending.begin());
}
};
auto flush_pending_tail = [&]() {
collapse_short_pairs();
strip_vs_last_decoder();
while (pending.size() >= 2 && pending[1].sample - pending[0].sample >= min_seg_samples)
{
decoder.processEdge(pending[0].sample, pending[0].rising, fs, on_bit, on_pkt);
last_dec_edge_sample = pending[0].sample;
last_dec_edge_valid = true;
pending.erase(pending.begin());
collapse_short_pairs();
strip_vs_last_decoder();
}
if (pending.size() == 1)
{
decoder.processEdge(pending[0].sample, pending[0].rising, fs, on_bit, on_pkt);
last_dec_edge_sample = pending[0].sample;
last_dec_edge_valid = true;
pending.clear();
}
};
for (;;)
{
CheckIfThreadShouldExit();
@ -146,10 +224,12 @@ void IrFoxAnalyzer::WorkerThread()
const BitState new_level = mIr->GetBitState();
const bool rising = (new_level == BIT_HIGH);
decoder.processEdge(edge_sample, rising, fs, on_bit, on_pkt);
pending.push_back(RawEdge{edge_sample, rising});
emit_confirmed_edges();
ReportProgress(edge_sample);
}
flush_pending_tail();
decoder.flushEnd(mIr->GetSampleNumber(), fs, on_bit, on_pkt);
if (frames_since_commit != 0)

View File

@ -262,7 +262,7 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const
const uint32_t rise_max_us = rise_sync_time_us + irfox::kToleranceUs;
/** Начало PRE-бабла: при первом подъёме пузыря метка ИК (активный низ) начинается со спада — берём prev_fall, если он близок. */
/** Визуализация: начало PRE с ближайшего спада в пределах ~3 битовых периодов (ИК-метка). */
auto new_bubble_preamble_start = [&](uint64_t edge_s, bool is_rising) -> uint64_t {
if (!is_rising)
return edge_s;
@ -278,9 +278,11 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const
if (rising)
{
const uint32_t cand_rp = static_cast<uint32_t>(t_us - prev_rise_us);
const double delta_rp = t_us - prev_rise_us;
const uint32_t cand_rp = static_cast<uint32_t>(delta_rp);
const uint32_t cand_ht = static_cast<uint32_t>(t_us - prev_fall_us);
const uint32_t cand_lt = static_cast<uint32_t>(prev_fall_us - prev_rise_us);
#if IRFOX_SHORT_LOW_GLITCH_REJECT
const bool short_low_glitch =
is_recive && !is_preamb && cand_ht < (rise_min_us / 8U) && cand_lt >= rise_min_us &&
@ -288,9 +290,7 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const
if (short_low_glitch)
{
err_other++;
#if IRFOX_GLITCH_REJECT_PHASE_NUDGE
irfox::irfoxGlitchPhaseNudgeUs(t_us, rise_sync_time_us, prev_rise_us);
#endif
last_processed_edge_us = t_us;
have_last_processed = true;
return;
@ -304,9 +304,7 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const
if (micro_gap_rise)
{
err_other++;
#if IRFOX_GLITCH_REJECT_PHASE_NUDGE
irfox::irfoxGlitchPhaseNudgeUs(t_us, rise_sync_time_us, prev_rise_us);
#endif
last_processed_edge_us = t_us;
have_last_processed = true;
return;
@ -319,18 +317,18 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const
have_last_processed = true;
return;
}
// Как IR_DecoderRaw::tick: до prev_rise_us = t_us, иначе ниже (t_us - prev_rise_us) на подъёме == 0.
// Визуализация PRE: длинная пауза, первый подъём — якорь от спада метки (декод как STM32DMA).
if (cand_rp > irmax * 2U && !is_recive_raw)
{
first_rx();
preamb_front_counter = static_cast<int8_t>(irfox::kPreambFronts - 1U);
is_preamb = true;
is_recive = true;
is_recive_raw = true;
is_wrong_pack = false;
preamble_bubble_start_sample_ = new_bubble_preamble_start(sample, true);
preamble_bubble_start_valid_ = true;
}
const bool accept_rise_timing =
(delta_rp > static_cast<double>(rise_max_us) / 4.0) || high_count != 0 || low_count != 0;
if (accept_rise_timing)
{
rise_period_anchor_sample_ = prev_rise_sample;
rise_period_us = cand_rp;
high_time_us = cand_ht;
@ -340,10 +338,11 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const
}
else
{
#if IRFOX_IN_MARK_DOUBLE_FALL_IGNORE
const double hi_since_rise = t_us - prev_rise_us;
const uint32_t mark_end_min_us = (rise_min_us * irfox::kBitActiveTakts) / irfox::kBitTakts;
#endif
err_other++;
}
}
else
{
if (t_us - prev_fall_us > rise_min_us / 4.0)
{
prev_fall_us = t_us;
@ -351,27 +350,24 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const
}
else
{
#if IRFOX_IN_MARK_DOUBLE_FALL_IGNORE
if (!(is_recive && !is_preamb && hi_since_rise < static_cast<double>(mark_end_min_us)))
#endif
{
err_other++;
}
}
}
// Первый фронт после длинной тишины на спаде; на подъёме — cand_rp выше (до обновления prev_rise_us).
// IR_DecoderRaw::tick (STM32DMA): без firstRX(); старт сырого приёма только этим блоком.
if (t_us > prev_rise_us && (t_us - prev_rise_us) > irmax * 2.0 && !is_recive_raw)
{
first_rx();
preamb_front_counter = static_cast<int8_t>(irfox::kPreambFronts - 1U);
preamb_front_counter = static_cast<int8_t>(irfox::kPreambFronts - 1);
is_preamb = true;
is_recive = true;
is_recive_raw = true;
is_wrong_pack = false;
if (!preamble_bubble_start_valid_)
{
preamble_bubble_start_sample_ = new_bubble_preamble_start(sample, rising);
preamble_bubble_start_valid_ = true;
}
}
if (preamb_front_counter)
{
@ -390,10 +386,10 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const
if (is_preamb)
{
is_preamb = false;
// IR_DecoderRaw: prevRise += risePeriod / 2 — prevRise на этом тике ещё время последнего подъёма
// (на спаде не обновлялся). В сэмплах: якорь = последний подъём + полпериода, не «спад + полпериод».
const uint64_t preamble_bubble_end_sample =
rising ? rise_period_anchor_sample_ : prev_rise_sample;
// IR_DecoderRaw: prevRise += risePeriod / 2 — фаза как в прошивке.
// Бабл PRE: до текущего фронта (sample1), чтобы охватить все kPreambPulse периодов (3 импульса),
// а не только до предыдущего подъёма (~2 периода).
const uint64_t preamble_bubble_end_sample = sample > 0 ? sample - 1 : sample;
prev_rise_us += rise_period_us / 2.0;
{
const double half_us = 0.5 * static_cast<double>(rise_period_us);
@ -460,16 +456,6 @@ void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const
else
write_to_buffer(false, false, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::WithBubble);
}
#if IRFOX_RISE_GRAY_SINGLE_BIT_FALLBACK
else if (irfox::riseGraySingleBitFallback(rise_period_us, rise_sync_time_us))
{
err_other++;
if (high_time_us > low_time_us)
write_to_buffer(true, false, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::WithBubble);
else
write_to_buffer(false, false, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::WithBubble);
}
#endif
else
{
uint16_t hc = ceil_div_u16(static_cast<uint16_t>(high_time_us > 0xFFFF ? 0xFFFF : high_time_us),

View File

@ -11,25 +11,8 @@ constexpr uint32_t kBitTakts = kBitActiveTakts + kBitPauseTakts;
constexpr uint32_t kBitTimeUs = kBitTakts * kCarrierPeriodUs;
constexpr uint32_t kToleranceUs = 300U;
/** Синхронно с IR_config.h: IR_SHORT_LOW_GLITCH_REJECT (1 = включено, как на железе). */
#ifndef IRFOX_SHORT_LOW_GLITCH_REJECT
#define IRFOX_SHORT_LOW_GLITCH_REJECT 1
#endif
#ifndef IRFOX_RISE_INCLUSIVE_AROUND
#define IRFOX_RISE_INCLUSIVE_AROUND 1
#endif
#ifndef IRFOX_RISE_GRAY_SINGLE_BIT_FALLBACK
#define IRFOX_RISE_GRAY_SINGLE_BIT_FALLBACK 0
#endif
#ifndef IRFOX_GLITCH_REJECT_PHASE_NUDGE
#define IRFOX_GLITCH_REJECT_PHASE_NUDGE 1
#endif
#ifndef IRFOX_MICRO_GAP_RISE_REJECT
#define IRFOX_MICRO_GAP_RISE_REJECT 1
#endif
#ifndef IRFOX_IN_MARK_DOUBLE_FALL_IGNORE
#define IRFOX_IN_MARK_DOUBLE_FALL_IGNORE 0
#endif
/** Мин. длительность плато (мкс) для потокового анти-глитча в анализаторе; согласовано с IR_INPUT_MIN_PULSE_US. */
constexpr uint32_t kMinFilteredPulseUs = 10U;
constexpr uint8_t kBitPerByte = 8U;
constexpr uint8_t kMsgBytes = 1;
@ -45,32 +28,31 @@ constexpr uint8_t kDataByteSizeMax =
constexpr uint8_t kPreambPulse = 3;
constexpr uint8_t kPreambFronts = kPreambPulse * 2U;
/** Отброс ложного подъёма после микро-LOW в паузе (как расширенный IR_DecoderRaw::tick). */
#ifndef IRFOX_SHORT_LOW_GLITCH_REJECT
#define IRFOX_SHORT_LOW_GLITCH_REJECT 1
#endif
#ifndef IRFOX_GLITCH_REJECT_PHASE_NUDGE
#define IRFOX_GLITCH_REJECT_PHASE_NUDGE 1
#endif
#ifndef IRFOX_MICRO_GAP_RISE_REJECT
#define IRFOX_MICRO_GAP_RISE_REJECT 1
#endif
inline uint32_t irTimeoutUs(uint32_t riseSyncTimeUs)
{
const uint32_t riseMax = riseSyncTimeUs + kToleranceUs;
return riseMax * (8U + kSyncBits + 1U);
}
/** Как IR_DecoderRaw.h: aroundRise(t) → riseTimeMin < t && t < riseTimeMax (ветка STM32DMA). */
inline bool aroundRisePeriod(uint32_t periodUs, uint32_t riseSyncTimeUs)
{
const uint32_t lo = riseSyncTimeUs > kToleranceUs ? riseSyncTimeUs - kToleranceUs : 0U;
const uint32_t hi = riseSyncTimeUs + kToleranceUs;
#if IRFOX_RISE_INCLUSIVE_AROUND
return lo <= periodUs && periodUs <= hi;
#else
return lo < periodUs && periodUs < hi;
#endif
}
/** Синхронно с IR_config.h: IR_RISE_GRAY_SINGLE_BIT_FALLBACK. */
inline bool riseGraySingleBitFallback(uint32_t periodUs, uint32_t riseSyncTimeUs)
{
const uint32_t hi = riseSyncTimeUs + kToleranceUs;
const uint32_t twice = riseSyncTimeUs * 2U;
return periodUs > hi && periodUs <= twice;
}
/** Синхронно с IR_config.h: IR_GLITCH_REJECT_PHASE_NUDGE. */
inline void irfoxGlitchPhaseNudgeUs(double edge_us, uint32_t rise_sync_us, double& prev_rise_us)
{
#if IRFOX_GLITCH_REJECT_PHASE_NUDGE