mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-09-18 19:13:58 +00:00
analyzer(IR_Fox): классификатор пакетов, настройки и результаты анализатора Saleae, тесты (работа Даши, снято с рабочей копии 09.09)
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
#include "IrFoxAnalyzer.h"
|
||||
#include "IrFoxAnalyzerSettings.h"
|
||||
#include "IrFoxDecoder.h"
|
||||
#include "IrFoxPacketClassifier.h"
|
||||
#include <AnalyzerChannelData.h>
|
||||
#include <AnalyzerResults.h>
|
||||
#include <algorithm>
|
||||
@ -25,7 +26,6 @@ IrFoxAnalyzer::~IrFoxAnalyzer()
|
||||
|
||||
void IrFoxAnalyzer::SetupResults()
|
||||
{
|
||||
m_packet_hex_by_frame.clear();
|
||||
mResults.reset(new IrFoxAnalyzerResults(this, &mSettings));
|
||||
SetAnalyzerResults(mResults.get());
|
||||
mResults->AddChannelBubblesWillAppearOn(mSettings.mInputChannel);
|
||||
@ -46,35 +46,75 @@ static void append_hex(std::string& s, const uint8_t* p, size_t n, size_t max_by
|
||||
s += "...";
|
||||
}
|
||||
|
||||
const char* IrFoxAnalyzer::PacketHexForFrame(U64 frame_id)
|
||||
static const char* packet_status_icon(IrFoxPacketOutcome outcome)
|
||||
{
|
||||
auto it = m_packet_hex_by_frame.find(frame_id);
|
||||
if (it == m_packet_hex_by_frame.end())
|
||||
return "";
|
||||
m_hex_scratch = it->second;
|
||||
return m_hex_scratch.c_str();
|
||||
switch (outcome)
|
||||
{
|
||||
case IrFoxPacketOutcome::Accepted:
|
||||
return "✅";
|
||||
case IrFoxPacketOutcome::IgnoredAddress:
|
||||
return "📭";
|
||||
case IrFoxPacketOutcome::RejectedCrc:
|
||||
case IrFoxPacketOutcome::RejectedLength:
|
||||
return "❌";
|
||||
case IrFoxPacketOutcome::RawOnlyUnknownType:
|
||||
case IrFoxPacketOutcome::RawOnlyTypedLength:
|
||||
return "⚠️";
|
||||
}
|
||||
return "⚠️";
|
||||
}
|
||||
|
||||
const char* IrFoxAnalyzer::BubbleTextForFrame(U64 frame_id) const
|
||||
static const char* message_type_icon(uint8_t message_type)
|
||||
{
|
||||
auto it = m_bubble_text_by_frame.find(frame_id);
|
||||
if (it == m_bubble_text_by_frame.end())
|
||||
return "";
|
||||
m_bubble_scratch = it->second;
|
||||
return m_bubble_scratch.c_str();
|
||||
switch (message_type)
|
||||
{
|
||||
case irfox::kMsgBack:
|
||||
return "🔙";
|
||||
case irfox::kMsgAccept:
|
||||
return "🤝";
|
||||
case irfox::kMsgRequest:
|
||||
return "📣";
|
||||
case irfox::kMsgBackTo:
|
||||
return "🎯";
|
||||
case irfox::kMsgDataNoAccept:
|
||||
return "📦";
|
||||
case irfox::kMsgDataAccept:
|
||||
return "📨";
|
||||
default:
|
||||
return "⚠️";
|
||||
}
|
||||
}
|
||||
|
||||
static std::string packet_icon(const IrFoxPacketDecision& decision, IrFoxPacketIconMode mode)
|
||||
{
|
||||
const char* status = packet_status_icon(decision.outcome);
|
||||
// Icon-mode selection describes successfully accepted packets. Diagnostic
|
||||
// outcomes must remain visible even when the user selected type-only mode.
|
||||
if (decision.outcome != IrFoxPacketOutcome::Accepted)
|
||||
return status;
|
||||
const char* type = message_type_icon(decision.message_type);
|
||||
switch (mode)
|
||||
{
|
||||
case IrFoxPacketIconMode::Status:
|
||||
return status;
|
||||
case IrFoxPacketIconMode::MessageType:
|
||||
return type;
|
||||
case IrFoxPacketIconMode::StatusAndType:
|
||||
default:
|
||||
return std::string(status) + type;
|
||||
}
|
||||
}
|
||||
|
||||
void IrFoxAnalyzer::WorkerThread()
|
||||
{
|
||||
mIr = GetAnalyzerChannelData(mSettings.mInputChannel);
|
||||
m_packet_hex_by_frame.clear();
|
||||
m_bubble_text_by_frame.clear();
|
||||
mResults->ClearCachedFrameText();
|
||||
|
||||
const U32 fs = GetSampleRate();
|
||||
IrFoxDecoder decoder;
|
||||
decoder.reset();
|
||||
|
||||
/** Потоковый фильтр: убирает импульсы короче kMinFilteredPulseUs (иголки/дребезг в сэмплах). */
|
||||
/** Mirrors the firmware input filter. kMinFilteredPulseUs=0 means direct edge delivery. */
|
||||
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
|
||||
@ -115,8 +155,19 @@ void IrFoxAnalyzer::WorkerThread()
|
||||
|
||||
U32 frames_since_commit = 0;
|
||||
const U32 kCommitBatch = 256;
|
||||
const bool detailed_presentation = mSettings.mPresentation == IrFoxPresentation::Detailed;
|
||||
std::vector<IrFoxEmitBit> pending_byte_frames;
|
||||
pending_byte_frames.reserve(irfox::kDataByteSizeMax);
|
||||
|
||||
IrFoxOnBit on_bit = [&](const IrFoxEmitBit& e) {
|
||||
auto note_legacy_frame = [&]() {
|
||||
if (++frames_since_commit >= kCommitBatch)
|
||||
{
|
||||
mResults->CommitResults();
|
||||
frames_since_commit = 0;
|
||||
}
|
||||
};
|
||||
|
||||
auto add_event_frame = [&](const IrFoxEmitBit& e) {
|
||||
Frame frame;
|
||||
frame.mStartingSampleInclusive = static_cast<S64>(e.start_sample);
|
||||
frame.mEndingSampleInclusive = static_cast<S64>(e.end_sample);
|
||||
@ -124,45 +175,188 @@ void IrFoxAnalyzer::WorkerThread()
|
||||
frame.mData1 = e.bit_value;
|
||||
frame.mData2 = e.bit_index | (U64(e.err_low) << 16) | (U64(e.err_high) << 24) | (U64(e.err_other) << 32);
|
||||
frame.mFlags = e.mflags;
|
||||
// В SDK только ERROR/WARNING меняют цвет бабла; sync выделяем янтарным (как warning), данные — обычные.
|
||||
if (e.frame_type == IRF_FT_SYNC_BIT)
|
||||
frame.mFlags |= DISPLAY_AS_WARNING_FLAG;
|
||||
mResults->AddFrame(frame);
|
||||
note_legacy_frame();
|
||||
};
|
||||
|
||||
const U64 fid = mResults->AddFrame(frame);
|
||||
if (e.bubble_text[0] != '\0')
|
||||
m_bubble_text_by_frame[fid] = e.bubble_text;
|
||||
if (++frames_since_commit >= kCommitBatch)
|
||||
auto flush_pending_bytes = [&]() {
|
||||
for (const IrFoxEmitBit& byte_event : pending_byte_frames)
|
||||
add_event_frame(byte_event);
|
||||
pending_byte_frames.clear();
|
||||
};
|
||||
|
||||
IrFoxOnBit on_bit = [&](const IrFoxEmitBit& e) {
|
||||
// Per-bit markers dominate Logic's render cost. They belong to Detailed
|
||||
// only; Overview keeps a fast packet-level timeline.
|
||||
if (e.frame_type == IRF_FT_DATA_BIT)
|
||||
{
|
||||
mResults->CommitResults();
|
||||
frames_since_commit = 0;
|
||||
if (detailed_presentation)
|
||||
{
|
||||
// Markers, like legacy frames, must be published in time order.
|
||||
// Publish the payload boundary when the first bit arrives rather
|
||||
// than inserting it retroactively after packet completion.
|
||||
if (e.bit_index == 0)
|
||||
mResults->AddMarker(static_cast<U64>(e.start_sample), AnalyzerResults::Start,
|
||||
mSettings.mInputChannel);
|
||||
const U64 marker_sample = static_cast<U64>((e.start_sample + e.end_sample) / 2);
|
||||
mResults->AddMarker(marker_sample, e.bit_value ? AnalyzerResults::One : AnalyzerResults::Zero,
|
||||
mSettings.mInputChannel);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Sync cells have no independent user-facing value at overview scale. A
|
||||
// fatal sync mismatch is still emitted as IRF_FT_ABORT below.
|
||||
if (e.frame_type == IRF_FT_SYNC_BIT)
|
||||
return;
|
||||
if (e.frame_type == IRF_FT_DATA_BYTE)
|
||||
{
|
||||
if (detailed_presentation)
|
||||
pending_byte_frames.push_back(e);
|
||||
return;
|
||||
}
|
||||
if (!detailed_presentation)
|
||||
return;
|
||||
if (e.frame_type == IRF_FT_PREAMBLE)
|
||||
{
|
||||
// A timeout can leave a few complete bytes without a packet event.
|
||||
// Flush them before the next PRE so legacy frames remain monotonic.
|
||||
flush_pending_bytes();
|
||||
add_event_frame(e);
|
||||
return;
|
||||
}
|
||||
if (e.frame_type == IRF_FT_OVERFLOW || e.frame_type == IRF_FT_ABORT)
|
||||
flush_pending_bytes();
|
||||
add_event_frame(e);
|
||||
};
|
||||
|
||||
IrFoxOnPacket on_pkt = [&](const IrFoxEmitPacket& p) {
|
||||
const IrFoxPacketDecision decision =
|
||||
irfox::classifyPacket(p.data_bytes, p.pack_size, p.crc_ok, mSettings.mReceiverAddress);
|
||||
Frame frame;
|
||||
frame.mStartingSampleInclusive = static_cast<S64>(p.start_sample);
|
||||
if (detailed_presentation)
|
||||
{
|
||||
// A Saleae legacy frame cannot overlap another legacy frame. Emit all
|
||||
// completed bytes except the last one, then use the last byte's span
|
||||
// for the packet outcome bubble.
|
||||
for (size_t i = 0; i + 1 < pending_byte_frames.size(); ++i)
|
||||
add_event_frame(pending_byte_frames[i]);
|
||||
frame.mStartingSampleInclusive = static_cast<S64>(pending_byte_frames.empty() ?
|
||||
p.data_start_sample : pending_byte_frames.back().start_sample);
|
||||
}
|
||||
else
|
||||
{
|
||||
frame.mStartingSampleInclusive = static_cast<S64>(p.start_sample);
|
||||
}
|
||||
frame.mEndingSampleInclusive = static_cast<S64>(p.end_sample);
|
||||
frame.mType = p.crc_ok ? IRF_FT_PACKET_OK : IRF_FT_PACKET_CRC_FAIL;
|
||||
frame.mFlags = 0;
|
||||
switch (decision.outcome)
|
||||
{
|
||||
case IrFoxPacketOutcome::Accepted:
|
||||
frame.mType = IRF_FT_PACKET_ACCEPTED;
|
||||
break;
|
||||
case IrFoxPacketOutcome::RejectedCrc:
|
||||
frame.mType = IRF_FT_PACKET_CRC_FAIL;
|
||||
frame.mFlags |= DISPLAY_AS_ERROR_FLAG;
|
||||
break;
|
||||
case IrFoxPacketOutcome::RejectedLength:
|
||||
frame.mType = IRF_FT_PACKET_BAD_LENGTH;
|
||||
frame.mFlags |= DISPLAY_AS_ERROR_FLAG;
|
||||
break;
|
||||
case IrFoxPacketOutcome::IgnoredAddress:
|
||||
frame.mType = IRF_FT_PACKET_IGNORED_ADDRESS;
|
||||
break;
|
||||
case IrFoxPacketOutcome::RawOnlyUnknownType:
|
||||
case IrFoxPacketOutcome::RawOnlyTypedLength:
|
||||
frame.mType = IRF_FT_PACKET_RAW_ONLY;
|
||||
frame.mFlags |= DISPLAY_AS_WARNING_FLAG;
|
||||
break;
|
||||
}
|
||||
frame.mData1 = p.pack_size;
|
||||
frame.mData2 = (U64(p.err_low) << 0) | (U64(p.err_high) << 8) | (U64(p.err_other) << 16);
|
||||
if (!p.crc_ok)
|
||||
frame.mFlags |= DISPLAY_AS_ERROR_FLAG;
|
||||
|
||||
const U64 fid = mResults->AddFrame(frame);
|
||||
pending_byte_frames.clear();
|
||||
|
||||
const std::string icon = packet_icon(decision, mSettings.mPacketIconMode);
|
||||
|
||||
std::string hx;
|
||||
append_hex(hx, p.data_bytes, p.pack_size);
|
||||
m_packet_hex_by_frame[fid] = std::move(hx);
|
||||
std::string status = irfox::packetOutcomeText(decision.outcome);
|
||||
if (p.pack_size >= irfox::kMsgBytes)
|
||||
{
|
||||
status += " ";
|
||||
status += irfox::messageTypeText(decision.message_type);
|
||||
}
|
||||
if (decision.has_destination)
|
||||
status += " to=" + std::to_string(decision.destination);
|
||||
|
||||
FrameV2 fv2;
|
||||
fv2.AddBoolean("crc_ok", p.crc_ok);
|
||||
fv2.AddInteger("len", static_cast<S64>(p.pack_size));
|
||||
fv2.AddInteger("err_low", static_cast<S64>(p.err_low));
|
||||
fv2.AddInteger("err_high", static_cast<S64>(p.err_high));
|
||||
fv2.AddInteger("err_other", static_cast<S64>(p.err_other));
|
||||
fv2.AddByteArray("data", p.data_bytes, p.pack_size);
|
||||
mResults->AddFrameV2(fv2, p.crc_ok ? "packet_ok" : "packet_bad", static_cast<U64>(p.start_sample),
|
||||
static_cast<U64>(p.end_sample));
|
||||
auto cached_text = std::make_shared<IrFoxCachedFrameText>();
|
||||
cached_text->export_hex = hx;
|
||||
cached_text->bubble_texts[0] = icon;
|
||||
if (detailed_presentation)
|
||||
{
|
||||
char last_byte[3] = "??";
|
||||
if (p.pack_size > 0)
|
||||
std::snprintf(last_byte, sizeof last_byte, "%02X", static_cast<unsigned>(p.data_bytes[p.pack_size - 1]));
|
||||
cached_text->bubble_texts[1] = std::string("0x") + last_byte + " " + icon;
|
||||
cached_text->bubble_texts[2] = cached_text->bubble_texts[1] + " " + status + " " +
|
||||
std::to_string(p.pack_size) + "B";
|
||||
if (!hx.empty())
|
||||
cached_text->bubble_texts[2] += " · " + hx;
|
||||
}
|
||||
else
|
||||
{
|
||||
cached_text->bubble_texts[1] = icon + " [" + hx + "] " + icon;
|
||||
cached_text->bubble_texts[2] = icon + " " + status + " " +
|
||||
std::to_string(p.pack_size) + "B";
|
||||
if (!hx.empty())
|
||||
cached_text->bubble_texts[2] += " · [" + hx + "] " + icon;
|
||||
}
|
||||
cached_text->bubble_text_count = 3;
|
||||
mResults->CacheFrameText(fid, cached_text);
|
||||
|
||||
if (detailed_presentation)
|
||||
{
|
||||
AnalyzerResults::MarkerType outcome_marker = AnalyzerResults::Square;
|
||||
switch (decision.outcome)
|
||||
{
|
||||
case IrFoxPacketOutcome::Accepted:
|
||||
outcome_marker = AnalyzerResults::Square;
|
||||
break;
|
||||
case IrFoxPacketOutcome::IgnoredAddress:
|
||||
case IrFoxPacketOutcome::RawOnlyUnknownType:
|
||||
case IrFoxPacketOutcome::RawOnlyTypedLength:
|
||||
outcome_marker = AnalyzerResults::X;
|
||||
break;
|
||||
case IrFoxPacketOutcome::RejectedCrc:
|
||||
case IrFoxPacketOutcome::RejectedLength:
|
||||
outcome_marker = AnalyzerResults::ErrorX;
|
||||
break;
|
||||
}
|
||||
mResults->AddMarker(static_cast<U64>(p.end_sample), outcome_marker, mSettings.mInputChannel);
|
||||
}
|
||||
if (detailed_presentation)
|
||||
{
|
||||
// Structured output is useful in Detailed. Overview intentionally keeps
|
||||
// only the single legacy packet frame used by the graph bubble.
|
||||
FrameV2 fv2;
|
||||
fv2.AddBoolean("crc_ok", p.crc_ok);
|
||||
fv2.AddBoolean("raw_accepted", decision.raw_accepted());
|
||||
fv2.AddBoolean("accepted", decision.outcome == IrFoxPacketOutcome::Accepted);
|
||||
fv2.AddInteger("outcome", static_cast<S64>(decision.outcome));
|
||||
fv2.AddInteger("message_type", static_cast<S64>(decision.message_type));
|
||||
fv2.AddInteger("receiver_address", static_cast<S64>(mSettings.mReceiverAddress));
|
||||
if (decision.has_destination)
|
||||
fv2.AddInteger("destination", static_cast<S64>(decision.destination));
|
||||
fv2.AddInteger("len", static_cast<S64>(p.pack_size));
|
||||
fv2.AddInteger("err_low", static_cast<S64>(p.err_low));
|
||||
fv2.AddInteger("err_high", static_cast<S64>(p.err_high));
|
||||
fv2.AddInteger("err_other", static_cast<S64>(p.err_other));
|
||||
fv2.AddByteArray("data", p.data_bytes, p.pack_size);
|
||||
const char* type = decision.outcome == IrFoxPacketOutcome::Accepted ? "packet_accepted" :
|
||||
decision.raw_accepted() ? "packet_raw_only" : "packet_rejected";
|
||||
mResults->AddFrameV2(fv2, type, static_cast<U64>(p.start_sample), static_cast<U64>(p.end_sample));
|
||||
}
|
||||
|
||||
if (++frames_since_commit >= kCommitBatch)
|
||||
{
|
||||
@ -170,6 +364,10 @@ void IrFoxAnalyzer::WorkerThread()
|
||||
frames_since_commit = 0;
|
||||
}
|
||||
};
|
||||
// In Overview the decoder still performs the same timing, CRC, and receiver
|
||||
// checks, but does not allocate and dispatch hundreds of visual bit events.
|
||||
const IrFoxOnBit no_bit_events;
|
||||
const IrFoxOnBit& bit_events = detailed_presentation ? on_bit : no_bit_events;
|
||||
|
||||
auto emit_confirmed_edges = [&]() {
|
||||
for (;;)
|
||||
@ -180,7 +378,7 @@ void IrFoxAnalyzer::WorkerThread()
|
||||
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);
|
||||
decoder.processEdge(pending[0].sample, pending[0].rising, fs, bit_events, on_pkt);
|
||||
last_dec_edge_sample = pending[0].sample;
|
||||
last_dec_edge_valid = true;
|
||||
pending.erase(pending.begin());
|
||||
@ -192,7 +390,7 @@ void IrFoxAnalyzer::WorkerThread()
|
||||
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);
|
||||
decoder.processEdge(pending[0].sample, pending[0].rising, fs, bit_events, on_pkt);
|
||||
last_dec_edge_sample = pending[0].sample;
|
||||
last_dec_edge_valid = true;
|
||||
pending.erase(pending.begin());
|
||||
@ -201,7 +399,7 @@ void IrFoxAnalyzer::WorkerThread()
|
||||
}
|
||||
if (pending.size() == 1)
|
||||
{
|
||||
decoder.processEdge(pending[0].sample, pending[0].rising, fs, on_bit, on_pkt);
|
||||
decoder.processEdge(pending[0].sample, pending[0].rising, fs, bit_events, on_pkt);
|
||||
last_dec_edge_sample = pending[0].sample;
|
||||
last_dec_edge_valid = true;
|
||||
pending.clear();
|
||||
@ -230,7 +428,9 @@ void IrFoxAnalyzer::WorkerThread()
|
||||
}
|
||||
|
||||
flush_pending_tail();
|
||||
decoder.flushEnd(mIr->GetSampleNumber(), fs, on_bit, on_pkt);
|
||||
decoder.flushEnd(mIr->GetSampleNumber(), fs, bit_events, on_pkt);
|
||||
if (detailed_presentation)
|
||||
flush_pending_bytes();
|
||||
|
||||
if (frames_since_commit != 0)
|
||||
mResults->CommitResults();
|
||||
|
||||
Reference in New Issue
Block a user