mirror of
https://github.com/Show-maket/IR-protocol.git
synced 2026-04-28 03:08:08 +00:00
Analyzer plug
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@ -5,3 +5,8 @@ log/*
|
|||||||
/.vscode
|
/.vscode
|
||||||
*.zip
|
*.zip
|
||||||
**/__pycache__
|
**/__pycache__
|
||||||
|
Analyzer/raw/dll/*.dll
|
||||||
|
Analyzer/raw/dll/*.so
|
||||||
|
Analyzer/raw/dll/*.dylib
|
||||||
|
/Analyzer/raw/IR_Fox/.github
|
||||||
|
**/.build
|
||||||
|
|||||||
52
Analyzer/raw/IR_Fox/.github/workflows/build.yml
vendored
Normal file
52
Analyzer/raw/IR_Fox/.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master, main]
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
pull_request:
|
||||||
|
branches: [master, main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
cmake -B ${{github.workspace}}/Analyzer/raw/IR_Fox/build -S ${{github.workspace}}/Analyzer/raw/IR_Fox -A x64
|
||||||
|
cmake --build ${{github.workspace}}/Analyzer/raw/IR_Fox/build --config Release
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: windows
|
||||||
|
path: ${{github.workspace}}/Analyzer/raw/dll/*.dll
|
||||||
|
|
||||||
|
macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
cmake -B ${{github.workspace}}/Analyzer/raw/IR_Fox/build -S ${{github.workspace}}/Analyzer/raw/IR_Fox -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build ${{github.workspace}}/Analyzer/raw/IR_Fox/build
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: macos
|
||||||
|
path: ${{github.workspace}}/Analyzer/raw/dll/*.so
|
||||||
|
|
||||||
|
linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
cmake -B ${{github.workspace}}/Analyzer/raw/IR_Fox/build -S ${{github.workspace}}/Analyzer/raw/IR_Fox -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build ${{github.workspace}}/Analyzer/raw/IR_Fox/build
|
||||||
|
env:
|
||||||
|
CC: gcc-10
|
||||||
|
CXX: g++-10
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: linux
|
||||||
|
path: ${{github.workspace}}/Analyzer/raw/dll/*.so
|
||||||
2
Analyzer/raw/IR_Fox/.gitignore
vendored
Normal file
2
Analyzer/raw/IR_Fox/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/build
|
||||||
|
.DS_Store
|
||||||
26
Analyzer/raw/IR_Fox/CMakeLists.txt
Normal file
26
Analyzer/raw/IR_Fox/CMakeLists.txt
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
|
project(IrFoxAnalyzer)
|
||||||
|
|
||||||
|
add_definitions(-DLOGIC2)
|
||||||
|
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
|
||||||
|
|
||||||
|
include(ExternalAnalyzerSDK)
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
src/IrFoxAnalyzer.cpp
|
||||||
|
src/IrFoxAnalyzer.h
|
||||||
|
src/IrFoxDecoder.cpp
|
||||||
|
src/IrFoxDecoder.h
|
||||||
|
src/IrFoxAnalyzerResults.cpp
|
||||||
|
src/IrFoxAnalyzerResults.h
|
||||||
|
src/IrFoxAnalyzerSettings.cpp
|
||||||
|
src/IrFoxAnalyzerSettings.h
|
||||||
|
src/IrFoxSimulationDataGenerator.cpp
|
||||||
|
src/IrFoxSimulationDataGenerator.h
|
||||||
|
)
|
||||||
|
|
||||||
|
add_analyzer_plugin(${PROJECT_NAME} SOURCES ${SOURCES})
|
||||||
49
Analyzer/raw/IR_Fox/CMakePresets.json
Normal file
49
Analyzer/raw/IR_Fox/CMakePresets.json
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"cmakeMinimumRequired": {
|
||||||
|
"major": 3,
|
||||||
|
"minor": 19,
|
||||||
|
"patch": 0
|
||||||
|
},
|
||||||
|
"configurePresets": [
|
||||||
|
{
|
||||||
|
"name": "win-vs2022-x64",
|
||||||
|
"displayName": "Visual Studio 2022 (x64)",
|
||||||
|
"generator": "Visual Studio 17 2022",
|
||||||
|
"architecture": "x64",
|
||||||
|
"binaryDir": "${sourceDir}/build"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "win-vs2019-x64",
|
||||||
|
"displayName": "Visual Studio 2019 (x64)",
|
||||||
|
"generator": "Visual Studio 16 2019",
|
||||||
|
"architecture": "x64",
|
||||||
|
"binaryDir": "${sourceDir}/build"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "win-nmake-release",
|
||||||
|
"displayName": "NMake Release (только из «x64 Native Tools Command Prompt for VS»)",
|
||||||
|
"generator": "NMake Makefiles",
|
||||||
|
"binaryDir": "${sourceDir}/build-nmake",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "Release"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buildPresets": [
|
||||||
|
{
|
||||||
|
"name": "win-release",
|
||||||
|
"configurePreset": "win-vs2022-x64",
|
||||||
|
"configuration": "Release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "win-release-vs2019",
|
||||||
|
"configurePreset": "win-vs2019-x64",
|
||||||
|
"configuration": "Release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nmake-release",
|
||||||
|
"configurePreset": "win-nmake-release"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
21
Analyzer/raw/IR_Fox/build_msvc.bat
Normal file
21
Analyzer/raw/IR_Fox/build_msvc.bat
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal EnableDelayedExpansion
|
||||||
|
cd /d "%~dp0"
|
||||||
|
|
||||||
|
if not exist build\CMakeCache.txt (
|
||||||
|
echo Run configure_msvc.bat first.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||||
|
for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSINSTALL=%%i"
|
||||||
|
if not defined VSINSTALL (
|
||||||
|
echo MSVC not found. Add C++ workload in Visual Studio Installer.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
call "!VSINSTALL!\Common7\Tools\VsDevCmd.bat" -arch=x64 -host_arch=x64
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
|
cmake --build build
|
||||||
|
pause
|
||||||
|
exit /b %ERRORLEVEL%
|
||||||
66
Analyzer/raw/IR_Fox/cmake/ExternalAnalyzerSDK.cmake
Normal file
66
Analyzer/raw/IR_Fox/cmake/ExternalAnalyzerSDK.cmake
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
||||||
|
|
||||||
|
if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY OR NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
|
||||||
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/)
|
||||||
|
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT TARGET Saleae::AnalyzerSDK)
|
||||||
|
FetchContent_Declare(
|
||||||
|
analyzersdk
|
||||||
|
GIT_REPOSITORY https://github.com/saleae/AnalyzerSDK.git
|
||||||
|
GIT_TAG master
|
||||||
|
GIT_SHALLOW True
|
||||||
|
GIT_PROGRESS True
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_GetProperties(analyzersdk)
|
||||||
|
|
||||||
|
if(NOT analyzersdk_POPULATED)
|
||||||
|
FetchContent_Populate(analyzersdk)
|
||||||
|
include(${analyzersdk_SOURCE_DIR}/AnalyzerSDKConfig.cmake)
|
||||||
|
|
||||||
|
if(APPLE OR WIN32)
|
||||||
|
get_target_property(analyzersdk_lib_location Saleae::AnalyzerSDK IMPORTED_LOCATION)
|
||||||
|
if(CMAKE_LIBRARY_OUTPUT_DIRECTORY)
|
||||||
|
file(COPY ${analyzersdk_lib_location} DESTINATION ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
|
||||||
|
else()
|
||||||
|
message(WARNING "Please define CMAKE_RUNTIME_OUTPUT_DIRECTORY and CMAKE_LIBRARY_OUTPUT_DIRECTORY if you want unit tests to locate ${analyzersdk_lib_location}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Shared folder for all Saleae LLA plugins in this repo: Analyzer/raw/dll
|
||||||
|
set(ANALYZER_DLL_OUT_DIR "${CMAKE_SOURCE_DIR}/../dll")
|
||||||
|
get_filename_component(ANALYZER_DLL_OUT_DIR "${ANALYZER_DLL_OUT_DIR}" ABSOLUTE)
|
||||||
|
file(MAKE_DIRECTORY "${ANALYZER_DLL_OUT_DIR}")
|
||||||
|
|
||||||
|
function(add_analyzer_plugin TARGET)
|
||||||
|
set(options)
|
||||||
|
set(single_value_args)
|
||||||
|
set(multi_value_args SOURCES)
|
||||||
|
cmake_parse_arguments(_p "${options}" "${single_value_args}" "${multi_value_args}" ${ARGN})
|
||||||
|
|
||||||
|
add_library(${TARGET} MODULE ${_p_SOURCES})
|
||||||
|
target_link_libraries(${TARGET} PRIVATE Saleae::AnalyzerSDK)
|
||||||
|
|
||||||
|
set(ANALYZER_DESTINATION "Analyzers")
|
||||||
|
install(TARGETS ${TARGET} RUNTIME DESTINATION ${ANALYZER_DESTINATION}
|
||||||
|
LIBRARY DESTINATION ${ANALYZER_DESTINATION})
|
||||||
|
|
||||||
|
set_target_properties(${TARGET} PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${ANALYZER_DLL_OUT_DIR}"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${ANALYZER_DLL_OUT_DIR}")
|
||||||
|
if(CMAKE_CONFIGURATION_TYPES)
|
||||||
|
foreach(CFG ${CMAKE_CONFIGURATION_TYPES})
|
||||||
|
string(TOUPPER ${CFG} CFG_UPPER)
|
||||||
|
set_target_properties(${TARGET} PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY_${CFG_UPPER} "${ANALYZER_DLL_OUT_DIR}"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY_${CFG_UPPER} "${ANALYZER_DLL_OUT_DIR}")
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
39
Analyzer/raw/IR_Fox/configure_msvc.bat
Normal file
39
Analyzer/raw/IR_Fox/configure_msvc.bat
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal EnableDelayedExpansion
|
||||||
|
cd /d "%~dp0"
|
||||||
|
|
||||||
|
echo === IrFoxAnalyzer: configure with MSVC ===
|
||||||
|
echo.
|
||||||
|
|
||||||
|
set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||||
|
if not exist "%VSWHERE%" (
|
||||||
|
echo [ERROR] vswhere not found. Install one of:
|
||||||
|
echo - Visual Studio 2022 with workload "Desktop development with C++"
|
||||||
|
echo - Build Tools for Visual Studio 2022: https://visualstudio.microsoft.com/visual-cpp-build-tools/
|
||||||
|
echo ^(select "Desktop development with C++" / MSVC, Windows SDK^)
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSINSTALL=%%i"
|
||||||
|
if not defined VSINSTALL (
|
||||||
|
echo [ERROR] MSVC toolset not found. Add "Desktop development with C++" in Visual Studio Installer.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Found: !VSINSTALL!
|
||||||
|
call "!VSINSTALL!\Common7\Tools\VsDevCmd.bat" -arch=x64 -host_arch=x64
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
|
if exist build rmdir /s /q build
|
||||||
|
if exist build-nmake rmdir /s /q build-nmake
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
|
||||||
|
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Configure OK. Build: build_msvc.bat ^(or from same VS env: cd build ^& cmake --build .^)
|
||||||
|
echo Output DLL: ..\dll\ ^(all analyzers share this folder^)
|
||||||
|
pause
|
||||||
|
exit /b 0
|
||||||
200
Analyzer/raw/IR_Fox/src/IrFoxAnalyzer.cpp
Normal file
200
Analyzer/raw/IR_Fox/src/IrFoxAnalyzer.cpp
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
#include "IrFoxAnalyzer.h"
|
||||||
|
#include "IrFoxAnalyzerSettings.h"
|
||||||
|
#include "IrFoxDecoder.h"
|
||||||
|
#include <AnalyzerChannelData.h>
|
||||||
|
#include <AnalyzerResults.h>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
IrFoxAnalyzer::IrFoxAnalyzer()
|
||||||
|
: Analyzer2(),
|
||||||
|
mSettings(),
|
||||||
|
mSimulationInitilized(false)
|
||||||
|
{
|
||||||
|
SetAnalyzerSettings(&mSettings);
|
||||||
|
UseFrameV2();
|
||||||
|
}
|
||||||
|
|
||||||
|
IrFoxAnalyzer::~IrFoxAnalyzer()
|
||||||
|
{
|
||||||
|
KillThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxAnalyzer::SetupResults()
|
||||||
|
{
|
||||||
|
m_packet_hex_by_frame.clear();
|
||||||
|
mResults.reset(new IrFoxAnalyzerResults(this, &mSettings));
|
||||||
|
SetAnalyzerResults(mResults.get());
|
||||||
|
mResults->AddChannelBubblesWillAppearOn(mSettings.mInputChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void append_hex(std::string& s, const uint8_t* p, size_t n, size_t max_bytes = 64)
|
||||||
|
{
|
||||||
|
static const char* hd = "0123456789abcdef";
|
||||||
|
const size_t m = n < max_bytes ? n : max_bytes;
|
||||||
|
for (size_t i = 0; i < m; i++)
|
||||||
|
{
|
||||||
|
s.push_back(hd[p[i] >> 4]);
|
||||||
|
s.push_back(hd[p[i] & 0xFu]);
|
||||||
|
if (i + 1 < m)
|
||||||
|
s.push_back(' ');
|
||||||
|
}
|
||||||
|
if (n > max_bytes)
|
||||||
|
s += "...";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* IrFoxAnalyzer::PacketHexForFrame(U64 frame_id)
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* IrFoxAnalyzer::BubbleTextForFrame(U64 frame_id) const
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxAnalyzer::WorkerThread()
|
||||||
|
{
|
||||||
|
mIr = GetAnalyzerChannelData(mSettings.mInputChannel);
|
||||||
|
m_packet_hex_by_frame.clear();
|
||||||
|
m_bubble_text_by_frame.clear();
|
||||||
|
|
||||||
|
const U32 fs = GetSampleRate();
|
||||||
|
IrFoxDecoder decoder;
|
||||||
|
decoder.reset();
|
||||||
|
|
||||||
|
U32 frames_since_commit = 0;
|
||||||
|
const U32 kCommitBatch = 256;
|
||||||
|
|
||||||
|
IrFoxOnBit on_bit = [&](const IrFoxEmitBit& e) {
|
||||||
|
Frame frame;
|
||||||
|
frame.mStartingSampleInclusive = static_cast<S64>(e.start_sample);
|
||||||
|
frame.mEndingSampleInclusive = static_cast<S64>(e.end_sample);
|
||||||
|
frame.mType = e.frame_type;
|
||||||
|
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;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
mResults->CommitResults();
|
||||||
|
frames_since_commit = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
IrFoxOnPacket on_pkt = [&](const IrFoxEmitPacket& p) {
|
||||||
|
Frame frame;
|
||||||
|
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.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);
|
||||||
|
|
||||||
|
std::string hx;
|
||||||
|
append_hex(hx, p.data_bytes, p.pack_size);
|
||||||
|
m_packet_hex_by_frame[fid] = std::move(hx);
|
||||||
|
|
||||||
|
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));
|
||||||
|
|
||||||
|
if (++frames_since_commit >= kCommitBatch)
|
||||||
|
{
|
||||||
|
mResults->CommitResults();
|
||||||
|
frames_since_commit = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
CheckIfThreadShouldExit();
|
||||||
|
|
||||||
|
const U64 segment_start = mIr->GetSampleNumber();
|
||||||
|
const BitState level = mIr->GetBitState();
|
||||||
|
|
||||||
|
mIr->AdvanceToNextEdge();
|
||||||
|
|
||||||
|
const U64 edge_sample = mIr->GetSampleNumber();
|
||||||
|
if (edge_sample == segment_start)
|
||||||
|
break;
|
||||||
|
|
||||||
|
const BitState new_level = mIr->GetBitState();
|
||||||
|
const bool rising = (new_level == BIT_HIGH);
|
||||||
|
|
||||||
|
decoder.processEdge(edge_sample, rising, fs, on_bit, on_pkt);
|
||||||
|
ReportProgress(edge_sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder.flushEnd(mIr->GetSampleNumber(), fs, on_bit, on_pkt);
|
||||||
|
|
||||||
|
if (frames_since_commit != 0)
|
||||||
|
mResults->CommitResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IrFoxAnalyzer::NeedsRerun()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
U32 IrFoxAnalyzer::GenerateSimulationData(U64 minimum_sample_index, U32 device_sample_rate,
|
||||||
|
SimulationChannelDescriptor** simulation_channels)
|
||||||
|
{
|
||||||
|
if (mSimulationInitilized == false)
|
||||||
|
{
|
||||||
|
mSimulationDataGenerator.Initialize(GetSimulationSampleRate(), &mSettings);
|
||||||
|
mSimulationInitilized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSimulationDataGenerator.GenerateSimulationData(minimum_sample_index, device_sample_rate,
|
||||||
|
simulation_channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
U32 IrFoxAnalyzer::GetMinimumSampleRateHz()
|
||||||
|
{
|
||||||
|
return 200000;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* IrFoxAnalyzer::GetAnalyzerName() const
|
||||||
|
{
|
||||||
|
return "IR Fox";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* GetAnalyzerName()
|
||||||
|
{
|
||||||
|
return "IR Fox";
|
||||||
|
}
|
||||||
|
|
||||||
|
Analyzer* CreateAnalyzer()
|
||||||
|
{
|
||||||
|
return new IrFoxAnalyzer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DestroyAnalyzer(Analyzer* analyzer)
|
||||||
|
{
|
||||||
|
delete analyzer;
|
||||||
|
}
|
||||||
49
Analyzer/raw/IR_Fox/src/IrFoxAnalyzer.h
Normal file
49
Analyzer/raw/IR_Fox/src/IrFoxAnalyzer.h
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
#ifndef IRFOX_ANALYZER_H
|
||||||
|
#define IRFOX_ANALYZER_H
|
||||||
|
|
||||||
|
#include <Analyzer.h>
|
||||||
|
#include "IrFoxAnalyzerSettings.h"
|
||||||
|
#include "IrFoxAnalyzerResults.h"
|
||||||
|
#include "IrFoxSimulationDataGenerator.h"
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
class ANALYZER_EXPORT IrFoxAnalyzer : public Analyzer2
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IrFoxAnalyzer();
|
||||||
|
virtual ~IrFoxAnalyzer();
|
||||||
|
|
||||||
|
virtual void SetupResults();
|
||||||
|
virtual void WorkerThread();
|
||||||
|
|
||||||
|
virtual U32 GenerateSimulationData(U64 newest_sample_requested, U32 sample_rate,
|
||||||
|
SimulationChannelDescriptor** simulation_channels);
|
||||||
|
virtual U32 GetMinimumSampleRateHz();
|
||||||
|
|
||||||
|
virtual const char* GetAnalyzerName() const;
|
||||||
|
virtual bool NeedsRerun();
|
||||||
|
|
||||||
|
const char* PacketHexForFrame(U64 frame_id);
|
||||||
|
const char* BubbleTextForFrame(U64 frame_id) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
IrFoxAnalyzerSettings mSettings;
|
||||||
|
std::unique_ptr<IrFoxAnalyzerResults> mResults;
|
||||||
|
AnalyzerChannelData* mIr;
|
||||||
|
|
||||||
|
IrFoxSimulationDataGenerator mSimulationDataGenerator;
|
||||||
|
bool mSimulationInitilized;
|
||||||
|
|
||||||
|
std::unordered_map<U64, std::string> m_packet_hex_by_frame;
|
||||||
|
std::unordered_map<U64, std::string> m_bubble_text_by_frame;
|
||||||
|
mutable std::string m_hex_scratch;
|
||||||
|
mutable std::string m_bubble_scratch;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern "C" ANALYZER_EXPORT const char* __cdecl GetAnalyzerName();
|
||||||
|
extern "C" ANALYZER_EXPORT Analyzer* __cdecl CreateAnalyzer();
|
||||||
|
extern "C" ANALYZER_EXPORT void __cdecl DestroyAnalyzer(Analyzer* analyzer);
|
||||||
|
|
||||||
|
#endif
|
||||||
175
Analyzer/raw/IR_Fox/src/IrFoxAnalyzerResults.cpp
Normal file
175
Analyzer/raw/IR_Fox/src/IrFoxAnalyzerResults.cpp
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
#include "IrFoxAnalyzerResults.h"
|
||||||
|
#include <AnalyzerHelpers.h>
|
||||||
|
#include <AnalyzerResults.h>
|
||||||
|
#include "IrFoxAnalyzer.h"
|
||||||
|
#include "IrFoxAnalyzerSettings.h"
|
||||||
|
#include "IrFoxDecoder.h"
|
||||||
|
#include <cstdio>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
IrFoxAnalyzerResults::IrFoxAnalyzerResults(IrFoxAnalyzer* analyzer, IrFoxAnalyzerSettings* settings)
|
||||||
|
: AnalyzerResults(),
|
||||||
|
mSettings(settings),
|
||||||
|
mAnalyzer(analyzer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
IrFoxAnalyzerResults::~IrFoxAnalyzerResults()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxAnalyzerResults::GenerateBubbleText(U64 frame_index, Channel& channel, DisplayBase display_base)
|
||||||
|
{
|
||||||
|
(void)display_base;
|
||||||
|
(void)channel;
|
||||||
|
ClearResultStrings();
|
||||||
|
Frame frame = GetFrame(frame_index);
|
||||||
|
|
||||||
|
char line[256];
|
||||||
|
|
||||||
|
switch (frame.mType)
|
||||||
|
{
|
||||||
|
case IRF_FT_DATA_BIT:
|
||||||
|
case IRF_FT_SYNC_BIT:
|
||||||
|
case IRF_FT_PREAMBLE:
|
||||||
|
case IRF_FT_OVERFLOW:
|
||||||
|
case IRF_FT_ABORT:
|
||||||
|
{
|
||||||
|
const char* bt = mAnalyzer->BubbleTextForFrame(frame_index);
|
||||||
|
if (bt && bt[0])
|
||||||
|
AddResultString(bt);
|
||||||
|
else if (frame.mType == IRF_FT_DATA_BIT)
|
||||||
|
AddResultString(frame.mData1 ? "1" : "0");
|
||||||
|
else if (frame.mType == IRF_FT_SYNC_BIT)
|
||||||
|
{
|
||||||
|
snprintf(line, sizeof line, "sync: %s", frame.mData1 ? "1" : "0");
|
||||||
|
AddResultString(line);
|
||||||
|
}
|
||||||
|
else if (frame.mType == IRF_FT_OVERFLOW)
|
||||||
|
AddResultString("OVF");
|
||||||
|
else if (frame.mType == IRF_FT_ABORT)
|
||||||
|
AddResultString("SYNC!");
|
||||||
|
else
|
||||||
|
AddResultString("PRE");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case IRF_FT_PACKET_OK:
|
||||||
|
case IRF_FT_PACKET_CRC_FAIL:
|
||||||
|
{
|
||||||
|
snprintf(line, sizeof line, "%s %lluB", frame.mType == IRF_FT_PACKET_OK ? "OK" : "CRC",
|
||||||
|
(unsigned long long)frame.mData1);
|
||||||
|
AddResultString(line);
|
||||||
|
const char* hx = mAnalyzer->PacketHexForFrame(frame_index);
|
||||||
|
if (hx && hx[0])
|
||||||
|
AddResultString(hx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
snprintf(line, sizeof line, "? type=%u", static_cast<unsigned>(frame.mType));
|
||||||
|
AddResultString(line);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxAnalyzerResults::GenerateExportFile(const char* file, DisplayBase display_base, U32 export_type_user_id)
|
||||||
|
{
|
||||||
|
(void)export_type_user_id;
|
||||||
|
(void)display_base;
|
||||||
|
std::ofstream file_stream(file, std::ios::out);
|
||||||
|
|
||||||
|
const U64 trigger_sample = mAnalyzer->GetTriggerSample();
|
||||||
|
const U32 sample_rate = mAnalyzer->GetSampleRate();
|
||||||
|
|
||||||
|
file_stream << "Time[s],Type,Data1,bit_idx,err_low,err_high,err_other,Flags,Hex" << std::endl;
|
||||||
|
|
||||||
|
const U64 num_frames = GetNumFrames();
|
||||||
|
for (U32 i = 0; i < num_frames; i++)
|
||||||
|
{
|
||||||
|
Frame frame = GetFrame(i);
|
||||||
|
|
||||||
|
char time_str[128];
|
||||||
|
AnalyzerHelpers::GetTimeString(frame.mStartingSampleInclusive, trigger_sample, sample_rate, time_str, 128);
|
||||||
|
|
||||||
|
const char* typ = "?";
|
||||||
|
switch (frame.mType)
|
||||||
|
{
|
||||||
|
case IRF_FT_DATA_BIT:
|
||||||
|
typ = "D";
|
||||||
|
break;
|
||||||
|
case IRF_FT_SYNC_BIT:
|
||||||
|
typ = "S";
|
||||||
|
break;
|
||||||
|
case IRF_FT_PACKET_OK:
|
||||||
|
typ = "OK";
|
||||||
|
break;
|
||||||
|
case IRF_FT_PACKET_CRC_FAIL:
|
||||||
|
typ = "CRC";
|
||||||
|
break;
|
||||||
|
case IRF_FT_OVERFLOW:
|
||||||
|
typ = "OVF";
|
||||||
|
break;
|
||||||
|
case IRF_FT_ABORT:
|
||||||
|
typ = "ABORT";
|
||||||
|
break;
|
||||||
|
case IRF_FT_PREAMBLE:
|
||||||
|
typ = "PRE";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* hx = mAnalyzer->PacketHexForFrame(i);
|
||||||
|
if (!hx)
|
||||||
|
hx = "";
|
||||||
|
|
||||||
|
U64 bit_idx = 0;
|
||||||
|
U32 err_l = 0, err_h = 0, err_o = 0;
|
||||||
|
if (frame.mType == IRF_FT_DATA_BIT || frame.mType == IRF_FT_SYNC_BIT ||
|
||||||
|
frame.mType == IRF_FT_OVERFLOW || frame.mType == IRF_FT_ABORT)
|
||||||
|
{
|
||||||
|
bit_idx = frame.mData2 & 0xFFFFull;
|
||||||
|
err_l = static_cast<U32>((frame.mData2 >> 16) & 0xFFull);
|
||||||
|
err_h = static_cast<U32>((frame.mData2 >> 24) & 0xFFull);
|
||||||
|
err_o = static_cast<U32>((frame.mData2 >> 32) & 0xFFull);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_stream << time_str << "," << typ << "," << frame.mData1 << "," << bit_idx << "," << err_l << "," << err_h
|
||||||
|
<< "," << err_o << "," << static_cast<unsigned>(frame.mFlags) << "," << hx << std::endl;
|
||||||
|
|
||||||
|
if (UpdateExportProgressAndCheckForCancel(i, num_frames) == true)
|
||||||
|
{
|
||||||
|
file_stream.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file_stream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxAnalyzerResults::GenerateFrameTabularText(U64 frame_index, DisplayBase display_base)
|
||||||
|
{
|
||||||
|
#ifdef SUPPORTS_PROTOCOL_SEARCH
|
||||||
|
(void)display_base;
|
||||||
|
Frame frame = GetFrame(frame_index);
|
||||||
|
ClearTabularText();
|
||||||
|
char buf[64];
|
||||||
|
snprintf(buf, sizeof buf, "t%u", static_cast<unsigned>(frame.mType));
|
||||||
|
AddTabularText(buf);
|
||||||
|
snprintf(buf, sizeof buf, "%llu", (unsigned long long)frame.mData1);
|
||||||
|
AddTabularText(buf);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxAnalyzerResults::GeneratePacketTabularText(U64 packet_id, DisplayBase display_base)
|
||||||
|
{
|
||||||
|
(void)packet_id;
|
||||||
|
(void)display_base;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxAnalyzerResults::GenerateTransactionTabularText(U64 transaction_id, DisplayBase display_base)
|
||||||
|
{
|
||||||
|
(void)transaction_id;
|
||||||
|
(void)display_base;
|
||||||
|
}
|
||||||
27
Analyzer/raw/IR_Fox/src/IrFoxAnalyzerResults.h
Normal file
27
Analyzer/raw/IR_Fox/src/IrFoxAnalyzerResults.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef IRFOX_ANALYZER_RESULTS
|
||||||
|
#define IRFOX_ANALYZER_RESULTS
|
||||||
|
|
||||||
|
#include <AnalyzerResults.h>
|
||||||
|
|
||||||
|
class IrFoxAnalyzer;
|
||||||
|
class IrFoxAnalyzerSettings;
|
||||||
|
|
||||||
|
class IrFoxAnalyzerResults : public AnalyzerResults
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IrFoxAnalyzerResults(IrFoxAnalyzer* analyzer, IrFoxAnalyzerSettings* settings);
|
||||||
|
virtual ~IrFoxAnalyzerResults();
|
||||||
|
|
||||||
|
virtual void GenerateBubbleText(U64 frame_index, Channel& channel, DisplayBase display_base);
|
||||||
|
virtual void GenerateExportFile(const char* file, DisplayBase display_base, U32 export_type_user_id);
|
||||||
|
|
||||||
|
virtual void GenerateFrameTabularText(U64 frame_index, DisplayBase display_base);
|
||||||
|
virtual void GeneratePacketTabularText(U64 packet_id, DisplayBase display_base);
|
||||||
|
virtual void GenerateTransactionTabularText(U64 transaction_id, DisplayBase display_base);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
IrFoxAnalyzerSettings* mSettings;
|
||||||
|
IrFoxAnalyzer* mAnalyzer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
62
Analyzer/raw/IR_Fox/src/IrFoxAnalyzerSettings.cpp
Normal file
62
Analyzer/raw/IR_Fox/src/IrFoxAnalyzerSettings.cpp
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
#include "IrFoxAnalyzerSettings.h"
|
||||||
|
#include <AnalyzerHelpers.h>
|
||||||
|
|
||||||
|
IrFoxAnalyzerSettings::IrFoxAnalyzerSettings()
|
||||||
|
: mInputChannel(UNDEFINED_CHANNEL),
|
||||||
|
mInputChannelInterface()
|
||||||
|
{
|
||||||
|
mInputChannelInterface.SetTitleAndTooltip(
|
||||||
|
"IR",
|
||||||
|
"Demodulated IR receiver output (e.g. TSOP: idle HIGH, active LOW)");
|
||||||
|
mInputChannelInterface.SetChannel(mInputChannel);
|
||||||
|
|
||||||
|
AddInterface(&mInputChannelInterface);
|
||||||
|
|
||||||
|
AddExportOption(0, "Export as text/csv file");
|
||||||
|
AddExportExtension(0, "text", "txt");
|
||||||
|
AddExportExtension(0, "csv", "csv");
|
||||||
|
|
||||||
|
ClearChannels();
|
||||||
|
AddChannel(mInputChannel, "IR", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
IrFoxAnalyzerSettings::~IrFoxAnalyzerSettings()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IrFoxAnalyzerSettings::SetSettingsFromInterfaces()
|
||||||
|
{
|
||||||
|
mInputChannel = mInputChannelInterface.GetChannel();
|
||||||
|
|
||||||
|
ClearChannels();
|
||||||
|
AddChannel(mInputChannel, "IR Fox", true);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxAnalyzerSettings::UpdateInterfacesFromSettings()
|
||||||
|
{
|
||||||
|
mInputChannelInterface.SetChannel(mInputChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxAnalyzerSettings::LoadSettings(const char* settings)
|
||||||
|
{
|
||||||
|
SimpleArchive text_archive;
|
||||||
|
text_archive.SetString(settings);
|
||||||
|
|
||||||
|
text_archive >> mInputChannel;
|
||||||
|
|
||||||
|
ClearChannels();
|
||||||
|
AddChannel(mInputChannel, "IR Fox", true);
|
||||||
|
|
||||||
|
UpdateInterfacesFromSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* IrFoxAnalyzerSettings::SaveSettings()
|
||||||
|
{
|
||||||
|
SimpleArchive text_archive;
|
||||||
|
|
||||||
|
text_archive << mInputChannel;
|
||||||
|
|
||||||
|
return SetReturnString(text_archive.GetString());
|
||||||
|
}
|
||||||
24
Analyzer/raw/IR_Fox/src/IrFoxAnalyzerSettings.h
Normal file
24
Analyzer/raw/IR_Fox/src/IrFoxAnalyzerSettings.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#ifndef IRFOX_ANALYZER_SETTINGS
|
||||||
|
#define IRFOX_ANALYZER_SETTINGS
|
||||||
|
|
||||||
|
#include <AnalyzerSettings.h>
|
||||||
|
#include <AnalyzerTypes.h>
|
||||||
|
|
||||||
|
class IrFoxAnalyzerSettings : public AnalyzerSettings
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IrFoxAnalyzerSettings();
|
||||||
|
virtual ~IrFoxAnalyzerSettings();
|
||||||
|
|
||||||
|
virtual bool SetSettingsFromInterfaces();
|
||||||
|
void UpdateInterfacesFromSettings();
|
||||||
|
virtual void LoadSettings(const char* settings);
|
||||||
|
virtual const char* SaveSettings();
|
||||||
|
|
||||||
|
Channel mInputChannel;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AnalyzerSettingInterfaceChannel mInputChannelInterface;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
603
Analyzer/raw/IR_Fox/src/IrFoxDecoder.cpp
Normal file
603
Analyzer/raw/IR_Fox/src/IrFoxDecoder.cpp
Normal file
@ -0,0 +1,603 @@
|
|||||||
|
#include "IrFoxDecoder.h"
|
||||||
|
#include <AnalyzerResults.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
void IrFoxDecoder::reset()
|
||||||
|
{
|
||||||
|
*this = IrFoxDecoder{};
|
||||||
|
rise_sync_time_us = irfox::kBitTimeUs;
|
||||||
|
next_control_bit = irfox::kBitPerByte;
|
||||||
|
have_last_processed = false;
|
||||||
|
last_processed_edge_us = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t IrFoxDecoder::ceil_div_u16(uint16_t val, uint16_t divider)
|
||||||
|
{
|
||||||
|
if (divider == 0)
|
||||||
|
return 0;
|
||||||
|
int ret = val / divider;
|
||||||
|
if ((val << 4) / divider - (ret << 4) >= 8)
|
||||||
|
ret++;
|
||||||
|
return static_cast<uint16_t>(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t IrFoxDecoder::crc8(const uint8_t* data, uint8_t start, uint8_t end, uint8_t poly)
|
||||||
|
{
|
||||||
|
uint8_t crc = 0xff;
|
||||||
|
for (size_t i = start; i < end; i++)
|
||||||
|
{
|
||||||
|
crc ^= data[i];
|
||||||
|
for (size_t j = 0; j < 8; j++)
|
||||||
|
{
|
||||||
|
if ((crc & 0x80) != 0)
|
||||||
|
crc = static_cast<uint8_t>((crc << 1) ^ poly);
|
||||||
|
else
|
||||||
|
crc <<= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IrFoxDecoder::crc_check(uint8_t len, uint16_t& crc_out)
|
||||||
|
{
|
||||||
|
crc_out = 0;
|
||||||
|
crc_out = static_cast<uint16_t>(static_cast<uint16_t>(crc8(data_buffer, 0, len, irfox::kPoly1) << 8) & 0xFF00u);
|
||||||
|
crc_out = static_cast<uint16_t>(crc_out | (crc8(data_buffer, 0, static_cast<uint8_t>(len + 1), irfox::kPoly2) & 0xFFu));
|
||||||
|
|
||||||
|
const bool ok = (data_buffer[len] == static_cast<uint8_t>((crc_out >> 8) & 0xFF)) &&
|
||||||
|
(data_buffer[len + 1] == static_cast<uint8_t>(crc_out & 0xFF));
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxDecoder::first_rx()
|
||||||
|
{
|
||||||
|
err_low_signal = err_high_signal = err_other = 0;
|
||||||
|
pack_size = 0;
|
||||||
|
is_buffer_overflow = false;
|
||||||
|
is_available = false;
|
||||||
|
buf_bit_pos = 0;
|
||||||
|
is_data = true;
|
||||||
|
i_data_buffer = 0;
|
||||||
|
next_control_bit = irfox::kBitPerByte;
|
||||||
|
i_sync_bit = 0;
|
||||||
|
err_sync_bit = 0;
|
||||||
|
is_wrong_pack = false;
|
||||||
|
is_preamb = true;
|
||||||
|
is_recive = false;
|
||||||
|
is_recive_raw = false;
|
||||||
|
msg_type_receive = 0;
|
||||||
|
rise_sync_time_us = irfox::kBitTimeUs;
|
||||||
|
std::memset(data_buffer, 0, sizeof data_buffer);
|
||||||
|
preamble_bubble_start_valid_ = false;
|
||||||
|
trim_first_data_bit_cell_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxDecoder::listen_start(double t_us)
|
||||||
|
{
|
||||||
|
(void)t_us;
|
||||||
|
const uint32_t irmax = irfox::irTimeoutUs(rise_sync_time_us);
|
||||||
|
if (is_recive_raw && (t_us - prev_rise_us) > irmax * 2.0)
|
||||||
|
{
|
||||||
|
is_recive_raw = false;
|
||||||
|
first_rx();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxDecoder::check_timeout(double t_us)
|
||||||
|
{
|
||||||
|
if (!is_recive)
|
||||||
|
return;
|
||||||
|
const uint32_t irmax = irfox::irTimeoutUs(rise_sync_time_us);
|
||||||
|
if (t_us - last_edge_time_us > irmax * 2.0)
|
||||||
|
{
|
||||||
|
is_recive = false;
|
||||||
|
msg_type_receive = 0;
|
||||||
|
last_edge_time_us = t_us;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxDecoder::write_to_buffer(bool bit, bool pack_trace_invert_fix, uint64_t cell_start_s, uint64_t cell_end_s,
|
||||||
|
const IrFoxOnBit& on_bit, const IrFoxOnPacket& on_pkt, IrFoxEmitBitMode emit_mode)
|
||||||
|
{
|
||||||
|
if (i_data_buffer > irfox::kDataByteSizeMax * 8u)
|
||||||
|
{
|
||||||
|
if (!is_buffer_overflow && on_bit)
|
||||||
|
{
|
||||||
|
IrFoxEmitBit e{};
|
||||||
|
e.start_sample = static_cast<int64_t>(cell_start_s);
|
||||||
|
e.end_sample = static_cast<int64_t>(cell_end_s);
|
||||||
|
e.frame_type = IRF_FT_OVERFLOW;
|
||||||
|
e.mflags = DISPLAY_AS_ERROR_FLAG;
|
||||||
|
fill_err_snapshot(e);
|
||||||
|
std::strncpy(e.bubble_text, "OVF", sizeof e.bubble_text);
|
||||||
|
e.bubble_text[sizeof e.bubble_text - 1] = '\0';
|
||||||
|
on_bit(e);
|
||||||
|
}
|
||||||
|
is_buffer_overflow = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_buffer_overflow || is_preamb || is_wrong_pack)
|
||||||
|
{
|
||||||
|
is_recive = false;
|
||||||
|
is_recive_raw = false;
|
||||||
|
msg_type_receive = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf_bit_pos == next_control_bit)
|
||||||
|
{
|
||||||
|
next_control_bit = static_cast<uint8_t>(next_control_bit + (is_data ? irfox::kSyncBits : irfox::kBitPerByte));
|
||||||
|
is_data = !is_data;
|
||||||
|
i_sync_bit = 0;
|
||||||
|
err_sync_bit = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_data)
|
||||||
|
{
|
||||||
|
const bool was_first_data_bit = (i_data_buffer == 0);
|
||||||
|
data_buffer[i_data_buffer / 8] |= static_cast<uint8_t>(bit ? 1 : 0) << (7 - (i_data_buffer % 8));
|
||||||
|
i_data_buffer++;
|
||||||
|
buf_bit_pos++;
|
||||||
|
|
||||||
|
uint8_t fl = 0;
|
||||||
|
if (pack_trace_invert_fix)
|
||||||
|
fl |= DISPLAY_AS_WARNING_FLAG;
|
||||||
|
if (on_bit && emit_mode == IrFoxEmitBitMode::WithBubble)
|
||||||
|
{
|
||||||
|
IrFoxEmitBit e{static_cast<int64_t>(cell_start_s), static_cast<int64_t>(cell_end_s), IRF_FT_DATA_BIT,
|
||||||
|
bit ? 1u : 0u, static_cast<uint64_t>(i_data_buffer - 1), fl, pack_trace_invert_fix, 0, 0, 0};
|
||||||
|
fill_err_snapshot(e);
|
||||||
|
e.bubble_text[0] = static_cast<char>(bit ? '1' : '0');
|
||||||
|
e.bubble_text[1] = '\0';
|
||||||
|
on_bit(e);
|
||||||
|
}
|
||||||
|
if (was_first_data_bit && trim_first_data_bit_cell_)
|
||||||
|
trim_first_data_bit_cell_ = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (i_sync_bit == 0)
|
||||||
|
{
|
||||||
|
const bool last_data_bit =
|
||||||
|
(data_buffer[((i_data_buffer - 1) / 8)] >> (7 - ((i_data_buffer - 1) % 8))) & 1;
|
||||||
|
if (bit != static_cast<bool>(last_data_bit))
|
||||||
|
{
|
||||||
|
buf_bit_pos++;
|
||||||
|
i_sync_bit++;
|
||||||
|
if (on_bit && emit_mode == IrFoxEmitBitMode::WithBubble)
|
||||||
|
{
|
||||||
|
IrFoxEmitBit e{static_cast<int64_t>(cell_start_s), static_cast<int64_t>(cell_end_s), IRF_FT_SYNC_BIT,
|
||||||
|
bit ? 1u : 0u, static_cast<uint64_t>(buf_bit_pos), 0, false, 0, 0, 0};
|
||||||
|
fill_err_snapshot(e);
|
||||||
|
std::snprintf(e.bubble_text, sizeof e.bubble_text, "s: %c", bit ? '1' : '0');
|
||||||
|
on_bit(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
i_sync_bit = 0;
|
||||||
|
err_other++;
|
||||||
|
err_sync_bit++;
|
||||||
|
const bool fatal_sync = (err_sync_bit >= irfox::kSyncBits);
|
||||||
|
if (fatal_sync)
|
||||||
|
is_wrong_pack = true;
|
||||||
|
if (on_bit && fatal_sync)
|
||||||
|
{
|
||||||
|
IrFoxEmitBit e{static_cast<int64_t>(cell_start_s), static_cast<int64_t>(cell_end_s), IRF_FT_ABORT,
|
||||||
|
0, 0, DISPLAY_AS_ERROR_FLAG, false, 0, 0, 0};
|
||||||
|
fill_err_snapshot(e);
|
||||||
|
std::strncpy(e.bubble_text, "SYNC!", sizeof e.bubble_text);
|
||||||
|
e.bubble_text[sizeof e.bubble_text - 1] = '\0';
|
||||||
|
on_bit(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buf_bit_pos++;
|
||||||
|
i_sync_bit++;
|
||||||
|
if (on_bit && emit_mode == IrFoxEmitBitMode::WithBubble)
|
||||||
|
{
|
||||||
|
IrFoxEmitBit e{static_cast<int64_t>(cell_start_s), static_cast<int64_t>(cell_end_s), IRF_FT_SYNC_BIT,
|
||||||
|
bit ? 1u : 0u, static_cast<uint64_t>(buf_bit_pos), 0, false, 0, 0, 0};
|
||||||
|
fill_err_snapshot(e);
|
||||||
|
std::snprintf(e.bubble_text, sizeof e.bubble_text, "s: %c", bit ? '1' : '0');
|
||||||
|
on_bit(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is_wrong_pack = (err_sync_bit >= irfox::kSyncBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_available && is_data && !is_wrong_pack)
|
||||||
|
{
|
||||||
|
if (i_data_buffer == 8 * irfox::kMsgBytes)
|
||||||
|
pack_size = static_cast<uint16_t>(data_buffer[0] & 0x1Fu);
|
||||||
|
|
||||||
|
if (pack_size && (i_data_buffer == 8))
|
||||||
|
msg_type_receive = static_cast<uint8_t>((data_buffer[0] >> 5) | 0xF8u);
|
||||||
|
|
||||||
|
if (pack_size && (i_data_buffer == pack_size * irfox::kBitPerByte))
|
||||||
|
{
|
||||||
|
uint16_t crc_computed = 0;
|
||||||
|
const bool crc_ok = crc_check(static_cast<uint8_t>(pack_size - irfox::kCrcBytes), crc_computed);
|
||||||
|
crc_value = crc_computed;
|
||||||
|
is_recive = false;
|
||||||
|
is_recive_raw = false;
|
||||||
|
msg_type_receive = 0;
|
||||||
|
is_available = crc_ok;
|
||||||
|
|
||||||
|
IrFoxEmitPacket pkt{};
|
||||||
|
pkt.start_sample = static_cast<int64_t>(cell_start_s);
|
||||||
|
pkt.end_sample = static_cast<int64_t>(cell_end_s);
|
||||||
|
pkt.crc_ok = crc_ok;
|
||||||
|
pkt.pack_size = static_cast<uint8_t>(pack_size);
|
||||||
|
pkt.err_low = err_low_signal;
|
||||||
|
pkt.err_high = err_high_signal;
|
||||||
|
pkt.err_other = err_other;
|
||||||
|
if (pack_size > 0 && pack_size <= irfox::kDataByteSizeMax)
|
||||||
|
std::memcpy(pkt.data_bytes, data_buffer, pack_size);
|
||||||
|
if (on_pkt)
|
||||||
|
on_pkt(pkt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxDecoder::processEdge(uint64_t sample, bool rising, uint32_t fs, const IrFoxOnBit& on_bit,
|
||||||
|
const IrFoxOnPacket& on_pkt)
|
||||||
|
{
|
||||||
|
const double t_us = sample_to_us(sample, fs);
|
||||||
|
const uint32_t irmax = irfox::irTimeoutUs(rise_sync_time_us);
|
||||||
|
uint32_t rise_min_us = rise_sync_time_us > irfox::kToleranceUs ? rise_sync_time_us - irfox::kToleranceUs : 0U;
|
||||||
|
|
||||||
|
listen_start(t_us);
|
||||||
|
|
||||||
|
if (have_last_processed && (t_us - last_processed_edge_us) > irmax * 2.0 && is_recive)
|
||||||
|
check_timeout(t_us);
|
||||||
|
|
||||||
|
last_edge_time_us = t_us;
|
||||||
|
last_edge_sample = sample;
|
||||||
|
|
||||||
|
const uint32_t rise_max_us = rise_sync_time_us + irfox::kToleranceUs;
|
||||||
|
|
||||||
|
/** Начало PRE-бабла: при первом подъёме пузыря метка ИК (активный низ) начинается со спада — берём prev_fall, если он близок. */
|
||||||
|
auto new_bubble_preamble_start = [&](uint64_t edge_s, bool is_rising) -> uint64_t {
|
||||||
|
if (!is_rising)
|
||||||
|
return edge_s;
|
||||||
|
if (edge_s > prev_fall_sample)
|
||||||
|
{
|
||||||
|
const double span_us = double(edge_s - prev_fall_sample) * 1e6 / double(fs);
|
||||||
|
const double max_us = double(rise_max_us) * 3.0;
|
||||||
|
if (span_us <= max_us)
|
||||||
|
return prev_fall_sample;
|
||||||
|
}
|
||||||
|
return edge_s;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (rising)
|
||||||
|
{
|
||||||
|
const uint32_t cand_rp = static_cast<uint32_t>(t_us - prev_rise_us);
|
||||||
|
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 &&
|
||||||
|
cand_rp >= rise_min_us && cand_rp <= irmax;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#if IRFOX_MICRO_GAP_RISE_REJECT
|
||||||
|
const bool micro_gap_cand_lt_ok =
|
||||||
|
(cand_lt >= rise_min_us) || (cand_lt >= (rise_min_us / 4U) && cand_lt < rise_min_us);
|
||||||
|
const bool micro_gap_rise = is_recive && !is_preamb && cand_ht < (rise_min_us / 8U) && micro_gap_cand_lt_ok &&
|
||||||
|
cand_rp >= (rise_min_us / 4U) && cand_rp < rise_min_us && cand_rp <= irmax;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (cand_rp <= rise_max_us / 4U && !high_count && !low_count)
|
||||||
|
{
|
||||||
|
err_other++;
|
||||||
|
last_processed_edge_us = t_us;
|
||||||
|
have_last_processed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Как IR_DecoderRaw::tick: до prev_rise_us = t_us, иначе ниже (t_us - prev_rise_us) на подъёме == 0.
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
rise_period_anchor_sample_ = prev_rise_sample;
|
||||||
|
rise_period_us = cand_rp;
|
||||||
|
high_time_us = cand_ht;
|
||||||
|
low_time_us = cand_lt;
|
||||||
|
prev_rise_us = t_us;
|
||||||
|
prev_rise_sample = sample;
|
||||||
|
}
|
||||||
|
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
|
||||||
|
if (t_us - prev_fall_us > rise_min_us / 4.0)
|
||||||
|
{
|
||||||
|
prev_fall_us = t_us;
|
||||||
|
prev_fall_sample = sample;
|
||||||
|
}
|
||||||
|
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).
|
||||||
|
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);
|
||||||
|
is_preamb = true;
|
||||||
|
is_recive = true;
|
||||||
|
is_recive_raw = true;
|
||||||
|
is_wrong_pack = false;
|
||||||
|
preamble_bubble_start_sample_ = new_bubble_preamble_start(sample, rising);
|
||||||
|
preamble_bubble_start_valid_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preamb_front_counter)
|
||||||
|
{
|
||||||
|
if (rising && rise_period_us < irmax)
|
||||||
|
{
|
||||||
|
if (rise_period_us < rise_min_us / 2U)
|
||||||
|
{
|
||||||
|
preamb_front_counter += 2;
|
||||||
|
err_other++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
preamb_front_counter--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
prev_rise_us += rise_period_us / 2.0;
|
||||||
|
{
|
||||||
|
const double half_us = 0.5 * static_cast<double>(rise_period_us);
|
||||||
|
const uint64_t half_s = static_cast<uint64_t>(std::llround(half_us * double(fs) / 1e6));
|
||||||
|
prev_rise_sample += half_s;
|
||||||
|
}
|
||||||
|
trim_first_data_bit_cell_ = true;
|
||||||
|
if (on_bit && preamble_bubble_start_valid_)
|
||||||
|
{
|
||||||
|
int64_t pe_start = static_cast<int64_t>(preamble_bubble_start_sample_);
|
||||||
|
int64_t pe_end = static_cast<int64_t>(preamble_bubble_end_sample);
|
||||||
|
if (preamble_bubble_end_sample == 0 || pe_end < pe_start)
|
||||||
|
pe_end = static_cast<int64_t>(sample > 0 ? sample - 1 : sample);
|
||||||
|
IrFoxEmitBit pe{};
|
||||||
|
pe.start_sample = pe_start;
|
||||||
|
pe.end_sample = pe_end;
|
||||||
|
pe.frame_type = IRF_FT_PREAMBLE;
|
||||||
|
fill_err_snapshot(pe);
|
||||||
|
std::strncpy(pe.bubble_text, "PRE", sizeof pe.bubble_text);
|
||||||
|
pe.bubble_text[sizeof pe.bubble_text - 1] = '\0';
|
||||||
|
on_bit(pe);
|
||||||
|
}
|
||||||
|
preamble_bubble_start_valid_ = false;
|
||||||
|
last_processed_edge_us = t_us;
|
||||||
|
have_last_processed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_preamb)
|
||||||
|
{
|
||||||
|
last_processed_edge_us = t_us;
|
||||||
|
have_last_processed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rise_period_us > irmax || is_buffer_overflow || rise_period_us < rise_min_us || is_wrong_pack)
|
||||||
|
{
|
||||||
|
last_processed_edge_us = t_us;
|
||||||
|
have_last_processed = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rising)
|
||||||
|
{
|
||||||
|
high_count = low_count = all_count = 0;
|
||||||
|
bool invert_err = false;
|
||||||
|
|
||||||
|
uint64_t cell_start_s = rise_period_anchor_sample_;
|
||||||
|
const uint64_t cell_end_s = sample > 0 ? sample - 1 : sample;
|
||||||
|
// После prev_rise += half period якорь может оказаться близко к текущему подъёму;
|
||||||
|
// max(anchor, prev_fall) > cell_end даёт пустой интервал — бабл первого бита пропадает.
|
||||||
|
if (trim_first_data_bit_cell_ && is_data && i_data_buffer == 0)
|
||||||
|
{
|
||||||
|
const uint64_t trimmed = std::max(cell_start_s, prev_fall_sample);
|
||||||
|
if (trimmed <= cell_end_s)
|
||||||
|
cell_start_s = trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (irfox::aroundRisePeriod(rise_period_us, rise_sync_time_us))
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
#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),
|
||||||
|
static_cast<uint16_t>(rise_sync_time_us));
|
||||||
|
uint16_t lc = ceil_div_u16(static_cast<uint16_t>(low_time_us > 0xFFFF ? 0xFFFF : low_time_us),
|
||||||
|
static_cast<uint16_t>(rise_sync_time_us));
|
||||||
|
uint16_t ac = ceil_div_u16(static_cast<uint16_t>(rise_period_us > 0xFFFF ? 0xFFFF : rise_period_us),
|
||||||
|
static_cast<uint16_t>(rise_sync_time_us));
|
||||||
|
high_count = static_cast<int8_t>(hc > 127 ? 127 : hc);
|
||||||
|
low_count = static_cast<int8_t>(lc > 127 ? 127 : lc);
|
||||||
|
all_count = static_cast<int8_t>(ac > 127 ? 127 : ac);
|
||||||
|
|
||||||
|
if (high_count == 0 && high_time_us > rise_sync_time_us / 3U)
|
||||||
|
{
|
||||||
|
high_count++;
|
||||||
|
err_other++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (low_count + high_count > all_count)
|
||||||
|
{
|
||||||
|
if (low_count > high_count)
|
||||||
|
{
|
||||||
|
low_count = static_cast<int8_t>(all_count - high_count);
|
||||||
|
err_low_signal = static_cast<uint8_t>(err_low_signal + static_cast<uint8_t>(low_count));
|
||||||
|
}
|
||||||
|
else if (low_count < high_count)
|
||||||
|
{
|
||||||
|
high_count = static_cast<int8_t>(all_count - low_count);
|
||||||
|
err_high_signal = static_cast<uint8_t>(err_high_signal + static_cast<uint8_t>(high_count));
|
||||||
|
}
|
||||||
|
else if (low_count == high_count)
|
||||||
|
{
|
||||||
|
invert_err = true;
|
||||||
|
err_other = static_cast<uint8_t>(err_other + static_cast<uint8_t>(all_count));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (low_count < high_count)
|
||||||
|
err_high_signal = static_cast<uint8_t>(err_high_signal + static_cast<uint8_t>(high_count));
|
||||||
|
else
|
||||||
|
err_low_signal = static_cast<uint8_t>(err_low_signal + static_cast<uint8_t>(low_count));
|
||||||
|
|
||||||
|
const bool burst_is_data_start = is_data;
|
||||||
|
const uint64_t merge_bit_index =
|
||||||
|
burst_is_data_start ? static_cast<uint64_t>(i_data_buffer) : static_cast<uint64_t>(buf_bit_pos + 1);
|
||||||
|
char s_bits[20]{};
|
||||||
|
char d_bits[20]{};
|
||||||
|
size_t s_n = 0;
|
||||||
|
size_t d_n = 0;
|
||||||
|
int first_merge_bit = -1;
|
||||||
|
bool merge_warn = false;
|
||||||
|
auto append_merge = [&](bool as_data, bool bitv) {
|
||||||
|
if (first_merge_bit < 0)
|
||||||
|
first_merge_bit = bitv ? 1 : 0;
|
||||||
|
char* buf = as_data ? d_bits : s_bits;
|
||||||
|
size_t& n = as_data ? d_n : s_n;
|
||||||
|
if (n + 1 < sizeof s_bits)
|
||||||
|
buf[n++] = static_cast<char>(bitv ? '1' : '0');
|
||||||
|
};
|
||||||
|
auto emit_merge_if_needed = [&]() {
|
||||||
|
if ((s_n == 0 && d_n == 0) || !on_bit)
|
||||||
|
return;
|
||||||
|
IrFoxEmitBit e{};
|
||||||
|
e.start_sample = static_cast<int64_t>(cell_start_s);
|
||||||
|
e.end_sample = static_cast<int64_t>(cell_end_s);
|
||||||
|
e.frame_type = (d_n > 0) ? IRF_FT_DATA_BIT : IRF_FT_SYNC_BIT;
|
||||||
|
e.bit_value = (first_merge_bit > 0) ? 1u : 0u;
|
||||||
|
e.bit_index = merge_bit_index;
|
||||||
|
e.mflags = merge_warn ? DISPLAY_AS_WARNING_FLAG : 0;
|
||||||
|
e.invert_fix = merge_warn;
|
||||||
|
fill_err_snapshot(e);
|
||||||
|
s_bits[s_n] = '\0';
|
||||||
|
d_bits[d_n] = '\0';
|
||||||
|
if (s_n && d_n)
|
||||||
|
std::snprintf(e.bubble_text, sizeof e.bubble_text, "s: %s d: %s", s_bits, d_bits);
|
||||||
|
else if (s_n)
|
||||||
|
std::snprintf(e.bubble_text, sizeof e.bubble_text, "s: %s", s_bits);
|
||||||
|
else
|
||||||
|
std::memcpy(e.bubble_text, d_bits, d_n + 1);
|
||||||
|
on_bit(e);
|
||||||
|
};
|
||||||
|
|
||||||
|
for (int8_t i = 0; i < low_count && 8 - i; i++)
|
||||||
|
{
|
||||||
|
const bool row_is_data = is_data;
|
||||||
|
if (i == low_count - 1 && invert_err)
|
||||||
|
{
|
||||||
|
invert_err = false;
|
||||||
|
write_to_buffer(true, true, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::Quiet);
|
||||||
|
merge_warn = true;
|
||||||
|
append_merge(row_is_data, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write_to_buffer(false, false, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::Quiet);
|
||||||
|
append_merge(row_is_data, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int8_t i = 0; i < high_count && 8 - i; i++)
|
||||||
|
{
|
||||||
|
const bool row_is_data = is_data;
|
||||||
|
if (i == high_count - 1 && invert_err)
|
||||||
|
{
|
||||||
|
invert_err = false;
|
||||||
|
write_to_buffer(false, true, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::Quiet);
|
||||||
|
merge_warn = true;
|
||||||
|
append_merge(row_is_data, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
write_to_buffer(true, false, cell_start_s, cell_end_s, on_bit, on_pkt, IrFoxEmitBitMode::Quiet);
|
||||||
|
append_merge(row_is_data, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit_merge_if_needed();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
last_processed_edge_us = t_us;
|
||||||
|
have_last_processed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxDecoder::flushEnd(uint64_t last_sample, uint32_t fs, const IrFoxOnBit& on_bit, const IrFoxOnPacket& on_pkt)
|
||||||
|
{
|
||||||
|
const double t_us = sample_to_us(last_sample, fs);
|
||||||
|
check_timeout(t_us);
|
||||||
|
(void)on_bit;
|
||||||
|
(void)on_pkt;
|
||||||
|
}
|
||||||
135
Analyzer/raw/IR_Fox/src/IrFoxDecoder.h
Normal file
135
Analyzer/raw/IR_Fox/src/IrFoxDecoder.h
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IrFoxProtocolConstants.h"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
|
enum IrFoxFrameType : uint8_t
|
||||||
|
{
|
||||||
|
IRF_FT_DATA_BIT = 1,
|
||||||
|
IRF_FT_SYNC_BIT = 2,
|
||||||
|
IRF_FT_PACKET_OK = 3,
|
||||||
|
IRF_FT_PACKET_CRC_FAIL = 4,
|
||||||
|
IRF_FT_OVERFLOW = 5,
|
||||||
|
IRF_FT_ABORT = 6,
|
||||||
|
IRF_FT_PREAMBLE = 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
/** WithBubble — вызвать on_bit; Quiet — только обновить состояние (для пакета битов с одного фронта). */
|
||||||
|
enum class IrFoxEmitBitMode : uint8_t
|
||||||
|
{
|
||||||
|
WithBubble,
|
||||||
|
Quiet,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IrFoxEmitBit
|
||||||
|
{
|
||||||
|
int64_t start_sample;
|
||||||
|
int64_t end_sample;
|
||||||
|
uint8_t frame_type;
|
||||||
|
uint64_t bit_value;
|
||||||
|
uint64_t bit_index;
|
||||||
|
uint8_t mflags;
|
||||||
|
bool invert_fix;
|
||||||
|
uint8_t err_low;
|
||||||
|
uint8_t err_high;
|
||||||
|
uint8_t err_other;
|
||||||
|
/** Подпись бабла: "0", "1" или склейка "001"; пусто — смотреть bit_value. */
|
||||||
|
char bubble_text[32]{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct IrFoxEmitPacket
|
||||||
|
{
|
||||||
|
int64_t start_sample;
|
||||||
|
int64_t end_sample;
|
||||||
|
bool crc_ok;
|
||||||
|
uint8_t pack_size;
|
||||||
|
uint8_t err_low;
|
||||||
|
uint8_t err_high;
|
||||||
|
uint8_t err_other;
|
||||||
|
uint8_t data_bytes[irfox::kDataByteSizeMax];
|
||||||
|
};
|
||||||
|
|
||||||
|
using IrFoxOnBit = std::function<void(const IrFoxEmitBit&)>;
|
||||||
|
using IrFoxOnPacket = std::function<void(const IrFoxEmitPacket&)>;
|
||||||
|
|
||||||
|
class IrFoxDecoder
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void reset();
|
||||||
|
void processEdge(uint64_t sample, bool rising, uint32_t sample_rate_hz, const IrFoxOnBit& on_bit,
|
||||||
|
const IrFoxOnPacket& on_pkt);
|
||||||
|
void flushEnd(uint64_t last_sample, uint32_t sample_rate_hz, const IrFoxOnBit& on_bit, const IrFoxOnPacket& on_pkt);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static uint16_t ceil_div_u16(uint16_t val, uint16_t divider);
|
||||||
|
static uint8_t crc8(const uint8_t* data, uint8_t start, uint8_t end, uint8_t poly);
|
||||||
|
bool crc_check(uint8_t len, uint16_t& crc_out);
|
||||||
|
|
||||||
|
void first_rx();
|
||||||
|
void listen_start(double t_us);
|
||||||
|
void check_timeout(double t_us);
|
||||||
|
void write_to_buffer(bool bit, bool pack_trace_invert_fix, uint64_t cell_start_s, uint64_t cell_end_s,
|
||||||
|
const IrFoxOnBit& on_bit, const IrFoxOnPacket& on_pkt,
|
||||||
|
IrFoxEmitBitMode emit_mode = IrFoxEmitBitMode::WithBubble);
|
||||||
|
|
||||||
|
double sample_to_us(uint64_t sample, uint32_t fs) const { return double(sample) * 1e6 / double(fs); }
|
||||||
|
|
||||||
|
// --- state (mirror IR_DecoderRaw) ---
|
||||||
|
uint8_t data_buffer[irfox::kDataByteSizeMax]{};
|
||||||
|
uint8_t err_low_signal = 0;
|
||||||
|
uint8_t err_high_signal = 0;
|
||||||
|
uint8_t err_other = 0;
|
||||||
|
|
||||||
|
bool is_available = false;
|
||||||
|
uint16_t pack_size = 0;
|
||||||
|
uint16_t crc_value = 0;
|
||||||
|
bool is_recive = false;
|
||||||
|
bool is_recive_raw = false;
|
||||||
|
bool is_preamb = false;
|
||||||
|
bool is_buffer_overflow = false;
|
||||||
|
bool is_wrong_pack = false;
|
||||||
|
|
||||||
|
uint32_t rise_sync_time_us = irfox::kBitTimeUs;
|
||||||
|
|
||||||
|
double prev_rise_us = 0;
|
||||||
|
double prev_fall_us = 0;
|
||||||
|
uint64_t prev_rise_sample = 0;
|
||||||
|
uint64_t prev_fall_sample = 0;
|
||||||
|
/** Сэмпл предыдущего нарастающего фронта до обновления на текущем тике (граница ячейки бита, см. IR_DecoderRaw::tick). */
|
||||||
|
uint64_t rise_period_anchor_sample_ = 0;
|
||||||
|
uint64_t preamble_bubble_start_sample_ = 0;
|
||||||
|
bool preamble_bubble_start_valid_ = false;
|
||||||
|
bool trim_first_data_bit_cell_ = false;
|
||||||
|
|
||||||
|
double last_edge_time_us = 0;
|
||||||
|
uint64_t last_edge_sample = 0;
|
||||||
|
double last_processed_edge_us = 0;
|
||||||
|
bool have_last_processed = false;
|
||||||
|
|
||||||
|
void fill_err_snapshot(IrFoxEmitBit& e) const
|
||||||
|
{
|
||||||
|
e.err_low = err_low_signal;
|
||||||
|
e.err_high = err_high_signal;
|
||||||
|
e.err_other = err_other;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t rise_period_us = 0;
|
||||||
|
uint32_t high_time_us = 0;
|
||||||
|
uint32_t low_time_us = 0;
|
||||||
|
|
||||||
|
int8_t high_count = 0;
|
||||||
|
int8_t low_count = 0;
|
||||||
|
int8_t all_count = 0;
|
||||||
|
|
||||||
|
uint16_t wrong_counter = 0;
|
||||||
|
int8_t preamb_front_counter = 0;
|
||||||
|
int16_t buf_bit_pos = 0;
|
||||||
|
bool is_data = true;
|
||||||
|
uint16_t i_data_buffer = 0;
|
||||||
|
uint8_t next_control_bit = irfox::kBitPerByte;
|
||||||
|
uint8_t i_sync_bit = 0;
|
||||||
|
uint8_t err_sync_bit = 0;
|
||||||
|
uint16_t error_counter = 0;
|
||||||
|
uint8_t msg_type_receive = 0;
|
||||||
|
};
|
||||||
89
Analyzer/raw/IR_Fox/src/IrFoxProtocolConstants.h
Normal file
89
Analyzer/raw/IR_Fox/src/IrFoxProtocolConstants.h
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace irfox {
|
||||||
|
|
||||||
|
constexpr uint32_t kCarrierPeriodUs = 1000000U / 38000U;
|
||||||
|
constexpr uint32_t kBitActiveTakts = 25U;
|
||||||
|
constexpr uint32_t kBitPauseTakts = 12U;
|
||||||
|
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
|
||||||
|
|
||||||
|
constexpr uint8_t kBitPerByte = 8U;
|
||||||
|
constexpr uint8_t kMsgBytes = 1;
|
||||||
|
constexpr uint8_t kAddrBytes = 2;
|
||||||
|
constexpr uint8_t kCrcBytes = 2;
|
||||||
|
constexpr uint8_t kPoly1 = 0x31;
|
||||||
|
constexpr uint8_t kPoly2 = 0x8C;
|
||||||
|
constexpr uint8_t kSyncBits = 3U;
|
||||||
|
constexpr uint8_t kBytePerPack = 31;
|
||||||
|
constexpr uint8_t kDataByteSizeMax =
|
||||||
|
static_cast<uint8_t>(kMsgBytes + kAddrBytes + kAddrBytes + kBytePerPack + kCrcBytes);
|
||||||
|
|
||||||
|
constexpr uint8_t kPreambPulse = 3;
|
||||||
|
constexpr uint8_t kPreambFronts = kPreambPulse * 2U;
|
||||||
|
|
||||||
|
inline uint32_t irTimeoutUs(uint32_t riseSyncTimeUs)
|
||||||
|
{
|
||||||
|
const uint32_t riseMax = riseSyncTimeUs + kToleranceUs;
|
||||||
|
return riseMax * (8U + kSyncBits + 1U);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
if (!(edge_us > static_cast<double>(rise_sync_us)))
|
||||||
|
return;
|
||||||
|
const double nudged = edge_us - static_cast<double>(rise_sync_us);
|
||||||
|
if (nudged > prev_rise_us && nudged < edge_us)
|
||||||
|
prev_rise_us = nudged;
|
||||||
|
#else
|
||||||
|
(void)edge_us;
|
||||||
|
(void)rise_sync_us;
|
||||||
|
(void)prev_rise_us;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace irfox
|
||||||
67
Analyzer/raw/IR_Fox/src/IrFoxSimulationDataGenerator.cpp
Normal file
67
Analyzer/raw/IR_Fox/src/IrFoxSimulationDataGenerator.cpp
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#include "IrFoxSimulationDataGenerator.h"
|
||||||
|
#include "IrFoxAnalyzerSettings.h"
|
||||||
|
#include <AnalyzerHelpers.h>
|
||||||
|
|
||||||
|
IrFoxSimulationDataGenerator::IrFoxSimulationDataGenerator()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
IrFoxSimulationDataGenerator::~IrFoxSimulationDataGenerator()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxSimulationDataGenerator::Initialize(U32 simulation_sample_rate, IrFoxAnalyzerSettings* settings)
|
||||||
|
{
|
||||||
|
mSimulationSampleRateHz = simulation_sample_rate;
|
||||||
|
mSettings = settings;
|
||||||
|
|
||||||
|
mIrSimulationData.SetChannel(mSettings->mInputChannel);
|
||||||
|
mIrSimulationData.SetSampleRate(simulation_sample_rate);
|
||||||
|
// Как у «покоя» на выходе TSOP: линия подтянута вверх
|
||||||
|
mIrSimulationData.SetInitialBitState(BIT_HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxSimulationDataGenerator::EmitIdle(U32 samples)
|
||||||
|
{
|
||||||
|
mIrSimulationData.Advance(samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxSimulationDataGenerator::EmitLow(U32 samples)
|
||||||
|
{
|
||||||
|
mIrSimulationData.TransitionIfNeeded(BIT_LOW);
|
||||||
|
mIrSimulationData.Advance(samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IrFoxSimulationDataGenerator::EmitHigh(U32 samples)
|
||||||
|
{
|
||||||
|
mIrSimulationData.TransitionIfNeeded(BIT_HIGH);
|
||||||
|
mIrSimulationData.Advance(samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
U32 IrFoxSimulationDataGenerator::GenerateSimulationData(U64 largest_sample_requested, U32 sample_rate,
|
||||||
|
SimulationChannelDescriptor** simulation_channel)
|
||||||
|
{
|
||||||
|
const U64 adjusted_largest_sample_requested =
|
||||||
|
AnalyzerHelpers::AdjustSimulationTargetSample(largest_sample_requested, sample_rate, mSimulationSampleRateHz);
|
||||||
|
|
||||||
|
// Упрощённый «пакет»: несколько импульсов вниз (активный уровень приёмника).
|
||||||
|
while (mIrSimulationData.GetCurrentSampleNumber() < adjusted_largest_sample_requested)
|
||||||
|
{
|
||||||
|
const U32 us_to_samples = mSimulationSampleRateHz / 1000000;
|
||||||
|
if (us_to_samples == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
EmitIdle(500 * us_to_samples);
|
||||||
|
EmitLow(4500 * us_to_samples);
|
||||||
|
EmitHigh(4500 * us_to_samples);
|
||||||
|
EmitLow(4500 * us_to_samples);
|
||||||
|
EmitHigh(4500 * us_to_samples);
|
||||||
|
EmitLow(560 * us_to_samples);
|
||||||
|
EmitHigh(560 * us_to_samples);
|
||||||
|
EmitLow(560 * us_to_samples);
|
||||||
|
EmitHigh(20000 * us_to_samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
*simulation_channel = &mIrSimulationData;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
27
Analyzer/raw/IR_Fox/src/IrFoxSimulationDataGenerator.h
Normal file
27
Analyzer/raw/IR_Fox/src/IrFoxSimulationDataGenerator.h
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef IRFOX_SIMULATION_DATA_GENERATOR
|
||||||
|
#define IRFOX_SIMULATION_DATA_GENERATOR
|
||||||
|
|
||||||
|
#include <SimulationChannelDescriptor.h>
|
||||||
|
|
||||||
|
class IrFoxAnalyzerSettings;
|
||||||
|
|
||||||
|
class IrFoxSimulationDataGenerator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
IrFoxSimulationDataGenerator();
|
||||||
|
~IrFoxSimulationDataGenerator();
|
||||||
|
|
||||||
|
void Initialize(U32 simulation_sample_rate, IrFoxAnalyzerSettings* settings);
|
||||||
|
U32 GenerateSimulationData(U64 newest_sample_requested, U32 sample_rate, SimulationChannelDescriptor** simulation_channel);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
IrFoxAnalyzerSettings* mSettings;
|
||||||
|
U32 mSimulationSampleRateHz;
|
||||||
|
SimulationChannelDescriptor mIrSimulationData;
|
||||||
|
|
||||||
|
void EmitIdle(U32 samples);
|
||||||
|
void EmitLow(U32 samples);
|
||||||
|
void EmitHigh(U32 samples);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
52
Analyzer/raw/PulseLengthStat/.github/workflows/build.yml
vendored
Normal file
52
Analyzer/raw/PulseLengthStat/.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [master, main]
|
||||||
|
tags:
|
||||||
|
- "*"
|
||||||
|
pull_request:
|
||||||
|
branches: [master, main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
windows:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
cmake -B ${{github.workspace}}/Analyzer/raw/PulseLengthStat/build -S ${{github.workspace}}/Analyzer/raw/PulseLengthStat -A x64
|
||||||
|
cmake --build ${{github.workspace}}/Analyzer/raw/PulseLengthStat/build --config Release
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: windows
|
||||||
|
path: ${{github.workspace}}/Analyzer/raw/dll/*.dll
|
||||||
|
|
||||||
|
macos:
|
||||||
|
runs-on: macos-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
cmake -B ${{github.workspace}}/Analyzer/raw/PulseLengthStat/build -S ${{github.workspace}}/Analyzer/raw/PulseLengthStat -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build ${{github.workspace}}/Analyzer/raw/PulseLengthStat/build
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: macos
|
||||||
|
path: ${{github.workspace}}/Analyzer/raw/dll/*.so
|
||||||
|
|
||||||
|
linux:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
cmake -B ${{github.workspace}}/Analyzer/raw/PulseLengthStat/build -S ${{github.workspace}}/Analyzer/raw/PulseLengthStat -DCMAKE_BUILD_TYPE=Release
|
||||||
|
cmake --build ${{github.workspace}}/Analyzer/raw/PulseLengthStat/build
|
||||||
|
env:
|
||||||
|
CC: gcc-10
|
||||||
|
CXX: g++-10
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: linux
|
||||||
|
path: ${{github.workspace}}/Analyzer/raw/dll/*.so
|
||||||
3
Analyzer/raw/PulseLengthStat/.gitignore
vendored
Normal file
3
Analyzer/raw/PulseLengthStat/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
/build
|
||||||
|
/build-nmake
|
||||||
|
.DS_Store
|
||||||
24
Analyzer/raw/PulseLengthStat/CMakeLists.txt
Normal file
24
Analyzer/raw/PulseLengthStat/CMakeLists.txt
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
|
project(PulseLengthStatAnalyzer)
|
||||||
|
|
||||||
|
add_definitions(-DLOGIC2)
|
||||||
|
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
|
|
||||||
|
set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
|
||||||
|
|
||||||
|
include(ExternalAnalyzerSDK)
|
||||||
|
|
||||||
|
set(SOURCES
|
||||||
|
src/PulseLengthStatAnalyzer.cpp
|
||||||
|
src/PulseLengthStatAnalyzer.h
|
||||||
|
src/PulseLengthStatAnalyzerResults.cpp
|
||||||
|
src/PulseLengthStatAnalyzerResults.h
|
||||||
|
src/PulseLengthStatAnalyzerSettings.cpp
|
||||||
|
src/PulseLengthStatAnalyzerSettings.h
|
||||||
|
src/PulseLengthStatSimulationDataGenerator.cpp
|
||||||
|
src/PulseLengthStatSimulationDataGenerator.h
|
||||||
|
)
|
||||||
|
|
||||||
|
add_analyzer_plugin(${PROJECT_NAME} SOURCES ${SOURCES})
|
||||||
49
Analyzer/raw/PulseLengthStat/CMakePresets.json
Normal file
49
Analyzer/raw/PulseLengthStat/CMakePresets.json
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
{
|
||||||
|
"version": 3,
|
||||||
|
"cmakeMinimumRequired": {
|
||||||
|
"major": 3,
|
||||||
|
"minor": 19,
|
||||||
|
"patch": 0
|
||||||
|
},
|
||||||
|
"configurePresets": [
|
||||||
|
{
|
||||||
|
"name": "win-vs2022-x64",
|
||||||
|
"displayName": "Visual Studio 2022 (x64)",
|
||||||
|
"generator": "Visual Studio 17 2022",
|
||||||
|
"architecture": "x64",
|
||||||
|
"binaryDir": "${sourceDir}/build"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "win-vs2019-x64",
|
||||||
|
"displayName": "Visual Studio 2019 (x64)",
|
||||||
|
"generator": "Visual Studio 16 2019",
|
||||||
|
"architecture": "x64",
|
||||||
|
"binaryDir": "${sourceDir}/build"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "win-nmake-release",
|
||||||
|
"displayName": "NMake Release (x64 Native Tools Command Prompt)",
|
||||||
|
"generator": "NMake Makefiles",
|
||||||
|
"binaryDir": "${sourceDir}/build-nmake",
|
||||||
|
"cacheVariables": {
|
||||||
|
"CMAKE_BUILD_TYPE": "Release"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"buildPresets": [
|
||||||
|
{
|
||||||
|
"name": "win-release",
|
||||||
|
"configurePreset": "win-vs2022-x64",
|
||||||
|
"configuration": "Release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "win-release-vs2019",
|
||||||
|
"configurePreset": "win-vs2019-x64",
|
||||||
|
"configuration": "Release"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nmake-release",
|
||||||
|
"configurePreset": "win-nmake-release"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
21
Analyzer/raw/PulseLengthStat/build_msvc.bat
Normal file
21
Analyzer/raw/PulseLengthStat/build_msvc.bat
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal EnableDelayedExpansion
|
||||||
|
cd /d "%~dp0"
|
||||||
|
|
||||||
|
if not exist build\CMakeCache.txt (
|
||||||
|
echo Run configure_msvc.bat first.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||||
|
for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSINSTALL=%%i"
|
||||||
|
if not defined VSINSTALL (
|
||||||
|
echo MSVC not found. Add C++ workload in Visual Studio Installer.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
call "!VSINSTALL!\Common7\Tools\VsDevCmd.bat" -arch=x64 -host_arch=x64
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
|
cmake --build build
|
||||||
|
pause
|
||||||
|
exit /b %ERRORLEVEL%
|
||||||
66
Analyzer/raw/PulseLengthStat/cmake/ExternalAnalyzerSDK.cmake
Normal file
66
Analyzer/raw/PulseLengthStat/cmake/ExternalAnalyzerSDK.cmake
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
||||||
|
|
||||||
|
if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY OR NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
|
||||||
|
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/)
|
||||||
|
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT TARGET Saleae::AnalyzerSDK)
|
||||||
|
FetchContent_Declare(
|
||||||
|
analyzersdk
|
||||||
|
GIT_REPOSITORY https://github.com/saleae/AnalyzerSDK.git
|
||||||
|
GIT_TAG master
|
||||||
|
GIT_SHALLOW True
|
||||||
|
GIT_PROGRESS True
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_GetProperties(analyzersdk)
|
||||||
|
|
||||||
|
if(NOT analyzersdk_POPULATED)
|
||||||
|
FetchContent_Populate(analyzersdk)
|
||||||
|
include(${analyzersdk_SOURCE_DIR}/AnalyzerSDKConfig.cmake)
|
||||||
|
|
||||||
|
if(APPLE OR WIN32)
|
||||||
|
get_target_property(analyzersdk_lib_location Saleae::AnalyzerSDK IMPORTED_LOCATION)
|
||||||
|
if(CMAKE_LIBRARY_OUTPUT_DIRECTORY)
|
||||||
|
file(COPY ${analyzersdk_lib_location} DESTINATION ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})
|
||||||
|
else()
|
||||||
|
message(WARNING "Please define CMAKE_RUNTIME_OUTPUT_DIRECTORY and CMAKE_LIBRARY_OUTPUT_DIRECTORY if you want unit tests to locate ${analyzersdk_lib_location}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Shared folder for all Saleae LLA plugins in this repo: Analyzer/raw/dll
|
||||||
|
set(ANALYZER_DLL_OUT_DIR "${CMAKE_SOURCE_DIR}/../dll")
|
||||||
|
get_filename_component(ANALYZER_DLL_OUT_DIR "${ANALYZER_DLL_OUT_DIR}" ABSOLUTE)
|
||||||
|
file(MAKE_DIRECTORY "${ANALYZER_DLL_OUT_DIR}")
|
||||||
|
|
||||||
|
function(add_analyzer_plugin TARGET)
|
||||||
|
set(options)
|
||||||
|
set(single_value_args)
|
||||||
|
set(multi_value_args SOURCES)
|
||||||
|
cmake_parse_arguments(_p "${options}" "${single_value_args}" "${multi_value_args}" ${ARGN})
|
||||||
|
|
||||||
|
add_library(${TARGET} MODULE ${_p_SOURCES})
|
||||||
|
target_link_libraries(${TARGET} PRIVATE Saleae::AnalyzerSDK)
|
||||||
|
|
||||||
|
set(ANALYZER_DESTINATION "Analyzers")
|
||||||
|
install(TARGETS ${TARGET} RUNTIME DESTINATION ${ANALYZER_DESTINATION}
|
||||||
|
LIBRARY DESTINATION ${ANALYZER_DESTINATION})
|
||||||
|
|
||||||
|
set_target_properties(${TARGET} PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY "${ANALYZER_DLL_OUT_DIR}"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY "${ANALYZER_DLL_OUT_DIR}")
|
||||||
|
if(CMAKE_CONFIGURATION_TYPES)
|
||||||
|
foreach(CFG ${CMAKE_CONFIGURATION_TYPES})
|
||||||
|
string(TOUPPER ${CFG} CFG_UPPER)
|
||||||
|
set_target_properties(${TARGET} PROPERTIES
|
||||||
|
RUNTIME_OUTPUT_DIRECTORY_${CFG_UPPER} "${ANALYZER_DLL_OUT_DIR}"
|
||||||
|
LIBRARY_OUTPUT_DIRECTORY_${CFG_UPPER} "${ANALYZER_DLL_OUT_DIR}")
|
||||||
|
endforeach()
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
39
Analyzer/raw/PulseLengthStat/configure_msvc.bat
Normal file
39
Analyzer/raw/PulseLengthStat/configure_msvc.bat
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
@echo off
|
||||||
|
setlocal EnableDelayedExpansion
|
||||||
|
cd /d "%~dp0"
|
||||||
|
|
||||||
|
echo === PulseLengthStatAnalyzer: configure with MSVC ===
|
||||||
|
echo.
|
||||||
|
|
||||||
|
set "VSWHERE=%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe"
|
||||||
|
if not exist "%VSWHERE%" (
|
||||||
|
echo [ERROR] vswhere not found. Install one of:
|
||||||
|
echo - Visual Studio 2022 with workload "Desktop development with C++"
|
||||||
|
echo - Build Tools for Visual Studio 2022: https://visualstudio.microsoft.com/visual-cpp-build-tools/
|
||||||
|
echo ^(select "Desktop development with C++" / MSVC, Windows SDK^)
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
for /f "usebackq tokens=*" %%i in (`"%VSWHERE%" -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath`) do set "VSINSTALL=%%i"
|
||||||
|
if not defined VSINSTALL (
|
||||||
|
echo [ERROR] MSVC toolset not found. Add "Desktop development with C++" in Visual Studio Installer.
|
||||||
|
exit /b 1
|
||||||
|
)
|
||||||
|
|
||||||
|
echo Found: !VSINSTALL!
|
||||||
|
call "!VSINSTALL!\Common7\Tools\VsDevCmd.bat" -arch=x64 -host_arch=x64
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
|
if exist build rmdir /s /q build
|
||||||
|
if exist build-nmake rmdir /s /q build-nmake
|
||||||
|
mkdir build
|
||||||
|
cd build
|
||||||
|
|
||||||
|
cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=Release
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo Configure OK. Build: build_msvc.bat ^(or from same VS env: cd build ^& cmake --build .^)
|
||||||
|
echo Output DLL: ..\dll\ ^(all analyzers share this folder^)
|
||||||
|
pause
|
||||||
|
exit /b 0
|
||||||
110
Analyzer/raw/PulseLengthStat/src/PulseLengthStatAnalyzer.cpp
Normal file
110
Analyzer/raw/PulseLengthStat/src/PulseLengthStatAnalyzer.cpp
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
#include "PulseLengthStatAnalyzer.h"
|
||||||
|
#include "PulseLengthStatAnalyzerSettings.h"
|
||||||
|
#include <AnalyzerChannelData.h>
|
||||||
|
|
||||||
|
// One frame per stable level between edges. mData1 = duration in samples; mFlags: 1 = HIGH, 0 = LOW.
|
||||||
|
|
||||||
|
PulseLengthStatAnalyzer::PulseLengthStatAnalyzer()
|
||||||
|
: Analyzer2(),
|
||||||
|
mSettings(),
|
||||||
|
mSimulationInitilized(false)
|
||||||
|
{
|
||||||
|
SetAnalyzerSettings(&mSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
PulseLengthStatAnalyzer::~PulseLengthStatAnalyzer()
|
||||||
|
{
|
||||||
|
KillThread();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseLengthStatAnalyzer::SetupResults()
|
||||||
|
{
|
||||||
|
mResults.reset(new PulseLengthStatAnalyzerResults(this, &mSettings));
|
||||||
|
SetAnalyzerResults(mResults.get());
|
||||||
|
mResults->AddChannelBubblesWillAppearOn(mSettings.mInputChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseLengthStatAnalyzer::WorkerThread()
|
||||||
|
{
|
||||||
|
mChannelData = GetAnalyzerChannelData(mSettings.mInputChannel);
|
||||||
|
|
||||||
|
U32 frames_since_commit = 0;
|
||||||
|
const U32 kCommitBatch = 256;
|
||||||
|
|
||||||
|
for (;;)
|
||||||
|
{
|
||||||
|
CheckIfThreadShouldExit();
|
||||||
|
|
||||||
|
const U64 segment_start = mChannelData->GetSampleNumber();
|
||||||
|
const BitState level = mChannelData->GetBitState();
|
||||||
|
|
||||||
|
mChannelData->AdvanceToNextEdge();
|
||||||
|
|
||||||
|
const U64 edge_sample = mChannelData->GetSampleNumber();
|
||||||
|
if (edge_sample == segment_start)
|
||||||
|
break;
|
||||||
|
|
||||||
|
const U64 duration_samples = edge_sample - segment_start;
|
||||||
|
const U64 end_inclusive = edge_sample > segment_start ? edge_sample - 1 : segment_start;
|
||||||
|
|
||||||
|
Frame frame;
|
||||||
|
frame.mData1 = duration_samples;
|
||||||
|
frame.mFlags = (level == BIT_HIGH) ? 1 : 0;
|
||||||
|
frame.mStartingSampleInclusive = static_cast<S64>(segment_start);
|
||||||
|
frame.mEndingSampleInclusive = static_cast<S64>(end_inclusive);
|
||||||
|
|
||||||
|
mResults->AddFrame(frame);
|
||||||
|
if (++frames_since_commit >= kCommitBatch)
|
||||||
|
{
|
||||||
|
mResults->CommitResults();
|
||||||
|
frames_since_commit = 0;
|
||||||
|
}
|
||||||
|
ReportProgress(edge_sample);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frames_since_commit != 0)
|
||||||
|
mResults->CommitResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PulseLengthStatAnalyzer::NeedsRerun()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
U32 PulseLengthStatAnalyzer::GenerateSimulationData(U64 minimum_sample_index, U32 device_sample_rate,
|
||||||
|
SimulationChannelDescriptor** simulation_channels)
|
||||||
|
{
|
||||||
|
if (mSimulationInitilized == false)
|
||||||
|
{
|
||||||
|
mSimulationDataGenerator.Initialize(GetSimulationSampleRate(), &mSettings);
|
||||||
|
mSimulationInitilized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mSimulationDataGenerator.GenerateSimulationData(minimum_sample_index, device_sample_rate,
|
||||||
|
simulation_channels);
|
||||||
|
}
|
||||||
|
|
||||||
|
U32 PulseLengthStatAnalyzer::GetMinimumSampleRateHz()
|
||||||
|
{
|
||||||
|
return 200000;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* PulseLengthStatAnalyzer::GetAnalyzerName() const
|
||||||
|
{
|
||||||
|
return "Pulse Length Stat";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* GetAnalyzerName()
|
||||||
|
{
|
||||||
|
return "Pulse Length Stat";
|
||||||
|
}
|
||||||
|
|
||||||
|
Analyzer* CreateAnalyzer()
|
||||||
|
{
|
||||||
|
return new PulseLengthStatAnalyzer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DestroyAnalyzer(Analyzer* analyzer)
|
||||||
|
{
|
||||||
|
delete analyzer;
|
||||||
|
}
|
||||||
39
Analyzer/raw/PulseLengthStat/src/PulseLengthStatAnalyzer.h
Normal file
39
Analyzer/raw/PulseLengthStat/src/PulseLengthStatAnalyzer.h
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#ifndef PULSELENGTHSTAT_ANALYZER_H
|
||||||
|
#define PULSELENGTHSTAT_ANALYZER_H
|
||||||
|
|
||||||
|
#include <Analyzer.h>
|
||||||
|
#include "PulseLengthStatAnalyzerSettings.h"
|
||||||
|
#include "PulseLengthStatAnalyzerResults.h"
|
||||||
|
#include "PulseLengthStatSimulationDataGenerator.h"
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class ANALYZER_EXPORT PulseLengthStatAnalyzer : public Analyzer2
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PulseLengthStatAnalyzer();
|
||||||
|
virtual ~PulseLengthStatAnalyzer();
|
||||||
|
|
||||||
|
virtual void SetupResults();
|
||||||
|
virtual void WorkerThread();
|
||||||
|
|
||||||
|
virtual U32 GenerateSimulationData(U64 newest_sample_requested, U32 sample_rate,
|
||||||
|
SimulationChannelDescriptor** simulation_channels);
|
||||||
|
virtual U32 GetMinimumSampleRateHz();
|
||||||
|
|
||||||
|
virtual const char* GetAnalyzerName() const;
|
||||||
|
virtual bool NeedsRerun();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PulseLengthStatAnalyzerSettings mSettings;
|
||||||
|
std::unique_ptr<PulseLengthStatAnalyzerResults> mResults;
|
||||||
|
AnalyzerChannelData* mChannelData;
|
||||||
|
|
||||||
|
PulseLengthStatSimulationDataGenerator mSimulationDataGenerator;
|
||||||
|
bool mSimulationInitilized;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern "C" ANALYZER_EXPORT const char* __cdecl GetAnalyzerName();
|
||||||
|
extern "C" ANALYZER_EXPORT Analyzer* __cdecl CreateAnalyzer();
|
||||||
|
extern "C" ANALYZER_EXPORT void __cdecl DestroyAnalyzer(Analyzer* analyzer);
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -0,0 +1,107 @@
|
|||||||
|
#include "PulseLengthStatAnalyzerResults.h"
|
||||||
|
#include <AnalyzerHelpers.h>
|
||||||
|
#include "PulseLengthStatAnalyzer.h"
|
||||||
|
#include "PulseLengthStatAnalyzerSettings.h"
|
||||||
|
#include <cstdio>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
PulseLengthStatAnalyzerResults::PulseLengthStatAnalyzerResults(PulseLengthStatAnalyzer* analyzer,
|
||||||
|
PulseLengthStatAnalyzerSettings* settings)
|
||||||
|
: AnalyzerResults(),
|
||||||
|
mSettings(settings),
|
||||||
|
mAnalyzer(analyzer)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PulseLengthStatAnalyzerResults::~PulseLengthStatAnalyzerResults()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static void FormatDurationUs(U64 duration_samples, U32 sample_rate_hz, char* out, size_t out_sz)
|
||||||
|
{
|
||||||
|
if (sample_rate_hz == 0)
|
||||||
|
{
|
||||||
|
snprintf(out, out_sz, "? us");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const double us = double(duration_samples) * 1e6 / double(sample_rate_hz);
|
||||||
|
snprintf(out, out_sz, "%.2f us", us);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseLengthStatAnalyzerResults::GenerateBubbleText(U64 frame_index, Channel& channel, DisplayBase display_base)
|
||||||
|
{
|
||||||
|
(void)display_base;
|
||||||
|
ClearResultStrings();
|
||||||
|
Frame frame = GetFrame(frame_index);
|
||||||
|
|
||||||
|
const U32 fs = mAnalyzer->GetSampleRate();
|
||||||
|
char dur[64];
|
||||||
|
FormatDurationUs(frame.mData1, fs, dur, sizeof dur);
|
||||||
|
|
||||||
|
const char* lev = (frame.mFlags != 0) ? "HIGH" : "LOW";
|
||||||
|
char line[160];
|
||||||
|
snprintf(line, sizeof line, "%s %s", lev, dur);
|
||||||
|
AddResultString(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseLengthStatAnalyzerResults::GenerateExportFile(const char* file, DisplayBase display_base, U32 export_type_user_id)
|
||||||
|
{
|
||||||
|
(void)export_type_user_id;
|
||||||
|
(void)display_base;
|
||||||
|
std::ofstream file_stream(file, std::ios::out);
|
||||||
|
|
||||||
|
const U64 trigger_sample = mAnalyzer->GetTriggerSample();
|
||||||
|
const U32 sample_rate = mAnalyzer->GetSampleRate();
|
||||||
|
|
||||||
|
file_stream << "Time [s],Level,Duration_samples,Duration_us" << std::endl;
|
||||||
|
|
||||||
|
const U64 num_frames = GetNumFrames();
|
||||||
|
for (U32 i = 0; i < num_frames; i++)
|
||||||
|
{
|
||||||
|
Frame frame = GetFrame(i);
|
||||||
|
|
||||||
|
char time_str[128];
|
||||||
|
AnalyzerHelpers::GetTimeString(frame.mStartingSampleInclusive, trigger_sample, sample_rate, time_str, 128);
|
||||||
|
|
||||||
|
char dur_us[64];
|
||||||
|
FormatDurationUs(frame.mData1, sample_rate, dur_us, sizeof dur_us);
|
||||||
|
|
||||||
|
file_stream << time_str << "," << ((frame.mFlags != 0) ? "HIGH" : "LOW") << "," << frame.mData1 << ","
|
||||||
|
<< dur_us << std::endl;
|
||||||
|
|
||||||
|
if (UpdateExportProgressAndCheckForCancel(i, num_frames) == true)
|
||||||
|
{
|
||||||
|
file_stream.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file_stream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseLengthStatAnalyzerResults::GenerateFrameTabularText(U64 frame_index, DisplayBase display_base)
|
||||||
|
{
|
||||||
|
#ifdef SUPPORTS_PROTOCOL_SEARCH
|
||||||
|
(void)display_base;
|
||||||
|
Frame frame = GetFrame(frame_index);
|
||||||
|
ClearTabularText();
|
||||||
|
|
||||||
|
const U32 fs = mAnalyzer->GetSampleRate();
|
||||||
|
char dur[64];
|
||||||
|
FormatDurationUs(frame.mData1, fs, dur, sizeof dur);
|
||||||
|
AddTabularText((frame.mFlags != 0) ? "H" : "L");
|
||||||
|
AddTabularText(dur);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseLengthStatAnalyzerResults::GeneratePacketTabularText(U64 packet_id, DisplayBase display_base)
|
||||||
|
{
|
||||||
|
(void)packet_id;
|
||||||
|
(void)display_base;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseLengthStatAnalyzerResults::GenerateTransactionTabularText(U64 transaction_id, DisplayBase display_base)
|
||||||
|
{
|
||||||
|
(void)transaction_id;
|
||||||
|
(void)display_base;
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef PULSELENGTHSTAT_ANALYZER_RESULTS
|
||||||
|
#define PULSELENGTHSTAT_ANALYZER_RESULTS
|
||||||
|
|
||||||
|
#include <AnalyzerResults.h>
|
||||||
|
|
||||||
|
class PulseLengthStatAnalyzer;
|
||||||
|
class PulseLengthStatAnalyzerSettings;
|
||||||
|
|
||||||
|
class PulseLengthStatAnalyzerResults : public AnalyzerResults
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PulseLengthStatAnalyzerResults(PulseLengthStatAnalyzer* analyzer, PulseLengthStatAnalyzerSettings* settings);
|
||||||
|
virtual ~PulseLengthStatAnalyzerResults();
|
||||||
|
|
||||||
|
virtual void GenerateBubbleText(U64 frame_index, Channel& channel, DisplayBase display_base);
|
||||||
|
virtual void GenerateExportFile(const char* file, DisplayBase display_base, U32 export_type_user_id);
|
||||||
|
|
||||||
|
virtual void GenerateFrameTabularText(U64 frame_index, DisplayBase display_base);
|
||||||
|
virtual void GeneratePacketTabularText(U64 packet_id, DisplayBase display_base);
|
||||||
|
virtual void GenerateTransactionTabularText(U64 transaction_id, DisplayBase display_base);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PulseLengthStatAnalyzerSettings* mSettings;
|
||||||
|
PulseLengthStatAnalyzer* mAnalyzer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
#include "PulseLengthStatAnalyzerSettings.h"
|
||||||
|
#include <AnalyzerHelpers.h>
|
||||||
|
|
||||||
|
PulseLengthStatAnalyzerSettings::PulseLengthStatAnalyzerSettings()
|
||||||
|
: mInputChannel(UNDEFINED_CHANNEL),
|
||||||
|
mInputChannelInterface()
|
||||||
|
{
|
||||||
|
mInputChannelInterface.SetTitleAndTooltip(
|
||||||
|
"Input",
|
||||||
|
"Digital channel: one frame per stable level between edges (duration in samples / us).");
|
||||||
|
mInputChannelInterface.SetChannel(mInputChannel);
|
||||||
|
|
||||||
|
AddInterface(&mInputChannelInterface);
|
||||||
|
|
||||||
|
AddExportOption(0, "Export as text/csv file");
|
||||||
|
AddExportExtension(0, "text", "txt");
|
||||||
|
AddExportExtension(0, "csv", "csv");
|
||||||
|
|
||||||
|
ClearChannels();
|
||||||
|
AddChannel(mInputChannel, "Input", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
PulseLengthStatAnalyzerSettings::~PulseLengthStatAnalyzerSettings()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PulseLengthStatAnalyzerSettings::SetSettingsFromInterfaces()
|
||||||
|
{
|
||||||
|
mInputChannel = mInputChannelInterface.GetChannel();
|
||||||
|
|
||||||
|
ClearChannels();
|
||||||
|
AddChannel(mInputChannel, "Pulse Length Stat", true);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseLengthStatAnalyzerSettings::UpdateInterfacesFromSettings()
|
||||||
|
{
|
||||||
|
mInputChannelInterface.SetChannel(mInputChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseLengthStatAnalyzerSettings::LoadSettings(const char* settings)
|
||||||
|
{
|
||||||
|
SimpleArchive text_archive;
|
||||||
|
text_archive.SetString(settings);
|
||||||
|
|
||||||
|
text_archive >> mInputChannel;
|
||||||
|
|
||||||
|
ClearChannels();
|
||||||
|
AddChannel(mInputChannel, "Pulse Length Stat", true);
|
||||||
|
|
||||||
|
UpdateInterfacesFromSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* PulseLengthStatAnalyzerSettings::SaveSettings()
|
||||||
|
{
|
||||||
|
SimpleArchive text_archive;
|
||||||
|
|
||||||
|
text_archive << mInputChannel;
|
||||||
|
|
||||||
|
return SetReturnString(text_archive.GetString());
|
||||||
|
}
|
||||||
@ -0,0 +1,24 @@
|
|||||||
|
#ifndef PULSELENGTHSTAT_ANALYZER_SETTINGS
|
||||||
|
#define PULSELENGTHSTAT_ANALYZER_SETTINGS
|
||||||
|
|
||||||
|
#include <AnalyzerSettings.h>
|
||||||
|
#include <AnalyzerTypes.h>
|
||||||
|
|
||||||
|
class PulseLengthStatAnalyzerSettings : public AnalyzerSettings
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PulseLengthStatAnalyzerSettings();
|
||||||
|
virtual ~PulseLengthStatAnalyzerSettings();
|
||||||
|
|
||||||
|
virtual bool SetSettingsFromInterfaces();
|
||||||
|
void UpdateInterfacesFromSettings();
|
||||||
|
virtual void LoadSettings(const char* settings);
|
||||||
|
virtual const char* SaveSettings();
|
||||||
|
|
||||||
|
Channel mInputChannel;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
AnalyzerSettingInterfaceChannel mInputChannelInterface;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@ -0,0 +1,65 @@
|
|||||||
|
#include "PulseLengthStatSimulationDataGenerator.h"
|
||||||
|
#include "PulseLengthStatAnalyzerSettings.h"
|
||||||
|
#include <AnalyzerHelpers.h>
|
||||||
|
|
||||||
|
PulseLengthStatSimulationDataGenerator::PulseLengthStatSimulationDataGenerator()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PulseLengthStatSimulationDataGenerator::~PulseLengthStatSimulationDataGenerator()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseLengthStatSimulationDataGenerator::Initialize(U32 simulation_sample_rate, PulseLengthStatAnalyzerSettings* settings)
|
||||||
|
{
|
||||||
|
mSimulationSampleRateHz = simulation_sample_rate;
|
||||||
|
mSettings = settings;
|
||||||
|
|
||||||
|
mSimChannel.SetChannel(mSettings->mInputChannel);
|
||||||
|
mSimChannel.SetSampleRate(simulation_sample_rate);
|
||||||
|
mSimChannel.SetInitialBitState(BIT_HIGH);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseLengthStatSimulationDataGenerator::EmitIdle(U32 samples)
|
||||||
|
{
|
||||||
|
mSimChannel.Advance(samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseLengthStatSimulationDataGenerator::EmitLow(U32 samples)
|
||||||
|
{
|
||||||
|
mSimChannel.TransitionIfNeeded(BIT_LOW);
|
||||||
|
mSimChannel.Advance(samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PulseLengthStatSimulationDataGenerator::EmitHigh(U32 samples)
|
||||||
|
{
|
||||||
|
mSimChannel.TransitionIfNeeded(BIT_HIGH);
|
||||||
|
mSimChannel.Advance(samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
U32 PulseLengthStatSimulationDataGenerator::GenerateSimulationData(U64 largest_sample_requested, U32 sample_rate,
|
||||||
|
SimulationChannelDescriptor** simulation_channel)
|
||||||
|
{
|
||||||
|
const U64 adjusted_largest_sample_requested =
|
||||||
|
AnalyzerHelpers::AdjustSimulationTargetSample(largest_sample_requested, sample_rate, mSimulationSampleRateHz);
|
||||||
|
|
||||||
|
while (mSimChannel.GetCurrentSampleNumber() < adjusted_largest_sample_requested)
|
||||||
|
{
|
||||||
|
const U32 us_to_samples = mSimulationSampleRateHz / 1000000;
|
||||||
|
if (us_to_samples == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
EmitIdle(500 * us_to_samples);
|
||||||
|
EmitLow(4500 * us_to_samples);
|
||||||
|
EmitHigh(4500 * us_to_samples);
|
||||||
|
EmitLow(4500 * us_to_samples);
|
||||||
|
EmitHigh(4500 * us_to_samples);
|
||||||
|
EmitLow(560 * us_to_samples);
|
||||||
|
EmitHigh(560 * us_to_samples);
|
||||||
|
EmitLow(560 * us_to_samples);
|
||||||
|
EmitHigh(20000 * us_to_samples);
|
||||||
|
}
|
||||||
|
|
||||||
|
*simulation_channel = &mSimChannel;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
#ifndef PULSELENGTHSTAT_SIMULATION_DATA_GENERATOR
|
||||||
|
#define PULSELENGTHSTAT_SIMULATION_DATA_GENERATOR
|
||||||
|
|
||||||
|
#include <SimulationChannelDescriptor.h>
|
||||||
|
|
||||||
|
class PulseLengthStatAnalyzerSettings;
|
||||||
|
|
||||||
|
class PulseLengthStatSimulationDataGenerator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PulseLengthStatSimulationDataGenerator();
|
||||||
|
~PulseLengthStatSimulationDataGenerator();
|
||||||
|
|
||||||
|
void Initialize(U32 simulation_sample_rate, PulseLengthStatAnalyzerSettings* settings);
|
||||||
|
U32 GenerateSimulationData(U64 newest_sample_requested, U32 sample_rate, SimulationChannelDescriptor** simulation_channel);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
PulseLengthStatAnalyzerSettings* mSettings;
|
||||||
|
U32 mSimulationSampleRateHz;
|
||||||
|
SimulationChannelDescriptor mSimChannel;
|
||||||
|
|
||||||
|
void EmitIdle(U32 samples);
|
||||||
|
void EmitLow(U32 samples);
|
||||||
|
void EmitHigh(U32 samples);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
0
Analyzer/raw/dll/.gitkeep
Normal file
0
Analyzer/raw/dll/.gitkeep
Normal file
100
tools/analyze_pkt33_car_point.py
Normal file
100
tools/analyze_pkt33_car_point.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Сравнение трасс decode по carraw3 vs pointraw_3 для кадра pkt[33] (car: CRC fail)."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
_TOOLS = Path(__file__).resolve().parent
|
||||||
|
if str(_TOOLS) not in sys.path:
|
||||||
|
sys.path.insert(0, str(_TOOLS))
|
||||||
|
|
||||||
|
from ir_decoder_raw_sim import (
|
||||||
|
BIT_TIME_US,
|
||||||
|
RISE_MAX,
|
||||||
|
RISE_MIN,
|
||||||
|
SimState,
|
||||||
|
first_rx,
|
||||||
|
parse_raw_csv,
|
||||||
|
segments_to_edges,
|
||||||
|
tick,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def run_traced(path: Path) -> tuple[SimState, list[dict]]:
|
||||||
|
rows = parse_raw_csv(path)
|
||||||
|
assert rows is not None
|
||||||
|
edges = segments_to_edges(rows)
|
||||||
|
st = SimState()
|
||||||
|
tr: list[dict] = []
|
||||||
|
for t_us, rising in edges:
|
||||||
|
tick(st, t_us, rising, BIT_TIME_US, trace_rise=tr)
|
||||||
|
return st, tr
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
root = Path(__file__).resolve().parents[1] / "Analyzer" / "raw"
|
||||||
|
car_p = root / "carraw3.txt"
|
||||||
|
pt_p = root / "pointraw_3.txt"
|
||||||
|
st_c, tr_c = run_traced(car_p)
|
||||||
|
st_p, tr_p = run_traced(pt_p)
|
||||||
|
|
||||||
|
bad_i = next(i for i, x in enumerate(st_c.packets) if not x[0])
|
||||||
|
print(f"carraw3: bad packet index {bad_i}, bytes {st_c.packets[bad_i][2].hex()}")
|
||||||
|
print(f"carraw3 packets={len(st_c.packets)} ok={sum(1 for x in st_c.packets if x[0])}")
|
||||||
|
print(f"pointraw_3 packets={len(st_p.packets)} ok={sum(1 for x in st_p.packets if x[0])}")
|
||||||
|
print(f"rise window us: min={RISE_MIN} max={RISE_MAX} bit={BIT_TIME_US}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Пока кадр №bad_i собирается, в списке уже bad_i завершённых пакетов → len(packets)==bad_i.
|
||||||
|
build_i = bad_i
|
||||||
|
|
||||||
|
def frame_trace(tr: list[dict]) -> list[dict]:
|
||||||
|
return [r for r in tr if r["n_pkt"] == build_i]
|
||||||
|
|
||||||
|
fc = frame_trace(tr_c)
|
||||||
|
fp = frame_trace(tr_p)
|
||||||
|
print(
|
||||||
|
f"decode rises while building packet[{bad_i}] (trace n_pkt=={build_i}): "
|
||||||
|
f"car {len(fc)} vs point {len(fp)} lines"
|
||||||
|
)
|
||||||
|
print("--- car (first 40 decode steps of this frame) ---")
|
||||||
|
for i, r in enumerate(fc[:40]):
|
||||||
|
br = r["branch"]
|
||||||
|
ex = ""
|
||||||
|
if br == "ceil":
|
||||||
|
ex = f" hc={r['hc']} lc={r['lc']} ac={r['ac']}"
|
||||||
|
print(
|
||||||
|
f" {i:2} t={r['t_us'] / 1e6:.6f}s rp={r['rp']} ht={r['ht']} lt={r['lt']} "
|
||||||
|
f"{br} bits_out={r['bits_out']} i_buf={r['i_buf']}{ex}"
|
||||||
|
)
|
||||||
|
print("--- point (first 40) ---")
|
||||||
|
for i, r in enumerate(fp[:40]):
|
||||||
|
br = r["branch"]
|
||||||
|
ex = ""
|
||||||
|
if br == "ceil":
|
||||||
|
ex = f" hc={r['hc']} lc={r['lc']} ac={r['ac']}"
|
||||||
|
print(
|
||||||
|
f" {i:2} t={r['t_us'] / 1e6:.6f}s rp={r['rp']} ht={r['ht']} lt={r['lt']} "
|
||||||
|
f"{br} bits_out={r['bits_out']} i_buf={r['i_buf']}{ex}"
|
||||||
|
)
|
||||||
|
|
||||||
|
print()
|
||||||
|
# Первое расхождение по (rp, ht, lt, branch, bits_out)
|
||||||
|
for i, (a, b) in enumerate(zip(fc, fp)):
|
||||||
|
ka = (a["rp"], a["ht"], a["lt"], a["branch"], a["bits_out"])
|
||||||
|
kb = (b["rp"], b["ht"], b["lt"], b["branch"], b["bits_out"])
|
||||||
|
if ka != kb:
|
||||||
|
print(f"First decode diff at step {i} within frame:")
|
||||||
|
print(f" car {a}")
|
||||||
|
print(f" point {b}")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if len(fc) != len(fp):
|
||||||
|
print(f"Same tuples for min(len)={min(len(fc), len(fp))}; length car={len(fc)} point={len(fp)}")
|
||||||
|
else:
|
||||||
|
print("Identical decode trace for full frame (unexpected if CRC differs)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
507
tools/ir_decoder_raw_sim.py
Normal file
507
tools/ir_decoder_raw_sim.py
Normal file
@ -0,0 +1,507 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Симуляция IR_DecoderRaw::tick() по CSV уровней (как Analyzer/raw/*_raw.txt).
|
||||||
|
Сверка с IR_DecoderRaw.cpp / IR_config.h — без Arduino, только логика декодера.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# IR_config.h
|
||||||
|
BIT_ACTIVE_TAKTS = 25
|
||||||
|
BIT_PAUSE_TAKTS = 12
|
||||||
|
CARRIER_HZ = 38000
|
||||||
|
CARRIER_PERIOD_US = 1_000_000 / CARRIER_HZ
|
||||||
|
BIT_TIME_US = int((BIT_ACTIVE_TAKTS + BIT_PAUSE_TAKTS) * CARRIER_PERIOD_US)
|
||||||
|
TOLERANCE_US = 300
|
||||||
|
SYNC_BITS = 3
|
||||||
|
BIT_PER_BYTE = 8
|
||||||
|
MSG_BYTES = 1
|
||||||
|
CRC_BYTES = 2
|
||||||
|
POLY1 = 0x31
|
||||||
|
POLY2 = 0x8C
|
||||||
|
DATA_BYTE_SIZE_MAX = 1 + 2 + 2 + 31 + 2
|
||||||
|
IR_MASK_MSG_INFO = 0x1F
|
||||||
|
PREAMB_FRONTS = 6 # preambPulse*2
|
||||||
|
|
||||||
|
RISE_MIN = BIT_TIME_US - TOLERANCE_US
|
||||||
|
RISE_MAX = BIT_TIME_US + TOLERANCE_US
|
||||||
|
IR_TIMEOUT = RISE_MAX * (8 + SYNC_BITS + 1)
|
||||||
|
# IR_config.h: IR_SHORT_LOW_GLITCH_REJECT (по умолчанию 1)
|
||||||
|
SHORT_LOW_GLITCH_REJECT = True
|
||||||
|
GLITCH_REJECT_PHASE_NUDGE = True
|
||||||
|
MICRO_GAP_RISE_REJECT = True
|
||||||
|
IN_MARK_DOUBLE_FALL_IGNORE = False # как IR_config по умолчанию; True — меньше err_other, на разбор битов не влияет
|
||||||
|
# IR_RISE_INCLUSIVE_AROUND, IR_RISE_GRAY_SINGLE_BIT_FALLBACK
|
||||||
|
RISE_INCLUSIVE_AROUND = True
|
||||||
|
RISE_GRAY_SINGLE_BIT_FALLBACK = False # как IR_config по умолчанию; True — только для экспериментов
|
||||||
|
|
||||||
|
|
||||||
|
def around_rise_period(rise_period: int, rise_sync: int) -> bool:
|
||||||
|
lo = max(0, rise_sync - TOLERANCE_US)
|
||||||
|
hi = rise_sync + TOLERANCE_US
|
||||||
|
if RISE_INCLUSIVE_AROUND:
|
||||||
|
return lo <= rise_period <= hi
|
||||||
|
return lo < rise_period < hi
|
||||||
|
|
||||||
|
|
||||||
|
def rise_gray_single_bit_fallback(rise_period: int, rise_sync: int) -> bool:
|
||||||
|
hi = rise_sync + TOLERANCE_US
|
||||||
|
return rise_period > hi and rise_period <= 2 * rise_sync
|
||||||
|
|
||||||
|
|
||||||
|
def glitch_phase_nudge(edge_us: float, rise_sync: int, prev_rise: float) -> float:
|
||||||
|
if not GLITCH_REJECT_PHASE_NUDGE:
|
||||||
|
return prev_rise
|
||||||
|
if edge_us <= rise_sync:
|
||||||
|
return prev_rise
|
||||||
|
nudged = edge_us - rise_sync
|
||||||
|
if nudged > prev_rise and nudged < edge_us:
|
||||||
|
return nudged
|
||||||
|
return prev_rise
|
||||||
|
|
||||||
|
|
||||||
|
def ceil_div(val: int, div: int) -> int:
|
||||||
|
ret = val // div
|
||||||
|
if ((val << 4) // div - (ret << 4)) >= 8:
|
||||||
|
ret += 1
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def crc8(data: bytes, start: int, end: int, poly: int) -> int:
|
||||||
|
crc = 0xFF
|
||||||
|
for i in range(start, end):
|
||||||
|
crc ^= data[i]
|
||||||
|
for _ in range(8):
|
||||||
|
if crc & 0x80:
|
||||||
|
crc = ((crc << 1) ^ poly) & 0xFF
|
||||||
|
else:
|
||||||
|
crc = (crc << 1) & 0xFF
|
||||||
|
return crc
|
||||||
|
|
||||||
|
|
||||||
|
def crc_check(buf: bytearray, pack_size: int) -> bool:
|
||||||
|
ln = pack_size - CRC_BYTES
|
||||||
|
c1 = crc8(bytes(buf), 0, ln, POLY1)
|
||||||
|
c2 = crc8(bytes(buf), 0, ln + 1, POLY2)
|
||||||
|
crc = ((c1 << 8) & 0xFF00) | (c2 & 0xFF)
|
||||||
|
return buf[ln] == ((crc >> 8) & 0xFF) and buf[ln + 1] == (crc & 0xFF)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_raw_csv(path: Path) -> list[tuple[float, str, float]] | None:
|
||||||
|
"""None — файл не в формате уровней (например экспорт битов декодера)."""
|
||||||
|
rows = []
|
||||||
|
with path.open(encoding="utf-8", errors="replace") as f:
|
||||||
|
header = f.readline()
|
||||||
|
h = header.lower()
|
||||||
|
if "duration" not in h and "level" not in h:
|
||||||
|
if "bit_idx" in h or (len(header.split(",")) >= 3 and header.split(",")[1].strip() == "Type"):
|
||||||
|
return None
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
parts = line.split(",")
|
||||||
|
if len(parts) < 4:
|
||||||
|
continue
|
||||||
|
t_s = float(parts[0])
|
||||||
|
level = parts[1].strip()
|
||||||
|
if level.upper() not in ("HIGH", "LOW"):
|
||||||
|
return None
|
||||||
|
dur_us = float(parts[3].replace(" us", "").strip())
|
||||||
|
rows.append((t_s, level, dur_us))
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def segments_to_edges(rows: list[tuple[float, str, float]]) -> list[tuple[float, bool]]:
|
||||||
|
"""Фронты: (t_us, rising)."""
|
||||||
|
edges: list[tuple[float, bool]] = []
|
||||||
|
prev = None
|
||||||
|
for t_s, level, _dur in rows:
|
||||||
|
high = level.upper() == "HIGH"
|
||||||
|
if prev is not None:
|
||||||
|
rising = high
|
||||||
|
edges.append((t_s * 1e6, rising))
|
||||||
|
prev = high
|
||||||
|
return edges
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SimState:
|
||||||
|
prev_rise: float = 0.0
|
||||||
|
prev_fall: float = 0.0
|
||||||
|
rise_period: int = 0
|
||||||
|
high_time: int = 0
|
||||||
|
low_time: int = 0
|
||||||
|
last_edge: float = 0.0
|
||||||
|
preamb_front_counter: int = 0
|
||||||
|
is_preamb: bool = False
|
||||||
|
is_recive_raw: bool = False
|
||||||
|
is_recive: bool = False
|
||||||
|
is_wrong_pack: bool = False
|
||||||
|
is_buffer_overflow: bool = False
|
||||||
|
buf_bit_pos: int = 0
|
||||||
|
is_data: bool = True
|
||||||
|
i_data_buffer: int = 0
|
||||||
|
next_control_bit: int = BIT_PER_BYTE
|
||||||
|
i_sync_bit: int = 0
|
||||||
|
err_sync_bit: int = 0
|
||||||
|
pack_size: int = 0
|
||||||
|
data_buffer: bytearray = field(default_factory=lambda: bytearray(DATA_BYTE_SIZE_MAX))
|
||||||
|
err_low: int = 0
|
||||||
|
err_high: int = 0
|
||||||
|
err_other: int = 0
|
||||||
|
high_count: int = 0
|
||||||
|
low_count: int = 0
|
||||||
|
all_count: int = 0
|
||||||
|
packets: list[tuple[bool, int, bytes]] = field(default_factory=list) # crc_ok, pack_size, raw bytes
|
||||||
|
bits_log: list[tuple[float, str, int]] = field(default_factory=list) # t_us, kind, bit
|
||||||
|
|
||||||
|
|
||||||
|
def first_rx(st: SimState) -> None:
|
||||||
|
"""IR_DecoderRaw::firstRX — сброс буфера; isRecive/isReciveRaw в прошивке здесь не меняются."""
|
||||||
|
st.is_preamb = True
|
||||||
|
st.is_wrong_pack = False
|
||||||
|
st.is_buffer_overflow = False
|
||||||
|
st.buf_bit_pos = 0
|
||||||
|
st.is_data = True
|
||||||
|
st.i_data_buffer = 0
|
||||||
|
st.next_control_bit = BIT_PER_BYTE
|
||||||
|
st.i_sync_bit = 0
|
||||||
|
st.err_sync_bit = 0
|
||||||
|
st.pack_size = 0
|
||||||
|
st.data_buffer = bytearray(DATA_BYTE_SIZE_MAX)
|
||||||
|
|
||||||
|
|
||||||
|
def tick(
|
||||||
|
st: SimState,
|
||||||
|
t_us: float,
|
||||||
|
rising: bool,
|
||||||
|
rise_sync_time: int,
|
||||||
|
*,
|
||||||
|
trace_rise: list[dict] | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Один вызов tick — один фронт (как IR_DecoderRaw после pop)."""
|
||||||
|
rise_min = max(0, rise_sync_time - TOLERANCE_US)
|
||||||
|
rise_max = rise_sync_time + TOLERANCE_US
|
||||||
|
irmax = IR_TIMEOUT # упрощ.: без подстройки riseSyncTime в timeout
|
||||||
|
|
||||||
|
# listenStart: обрыв незавершённого приёма
|
||||||
|
if st.is_recive_raw and (t_us - st.prev_rise) > irmax * 2:
|
||||||
|
st.is_recive_raw = False
|
||||||
|
first_rx(st)
|
||||||
|
|
||||||
|
st.last_edge = t_us
|
||||||
|
skip_rest = False
|
||||||
|
|
||||||
|
if rising:
|
||||||
|
cand_rp = int(t_us - st.prev_rise)
|
||||||
|
cand_ht = int(t_us - st.prev_fall)
|
||||||
|
cand_lt = int(st.prev_fall - st.prev_rise)
|
||||||
|
if SHORT_LOW_GLITCH_REJECT:
|
||||||
|
short_low_glitch = (
|
||||||
|
st.is_recive
|
||||||
|
and not st.is_preamb
|
||||||
|
and cand_ht < (rise_min // 8)
|
||||||
|
and cand_lt >= rise_min
|
||||||
|
and cand_rp >= rise_min
|
||||||
|
and cand_rp <= IR_TIMEOUT
|
||||||
|
)
|
||||||
|
if short_low_glitch:
|
||||||
|
st.err_other += 1
|
||||||
|
if GLITCH_REJECT_PHASE_NUDGE:
|
||||||
|
st.prev_rise = glitch_phase_nudge(t_us, rise_sync_time, st.prev_rise)
|
||||||
|
skip_rest = True
|
||||||
|
if not skip_rest and MICRO_GAP_RISE_REJECT:
|
||||||
|
lt_ok = (cand_lt >= rise_min) or (cand_lt >= (rise_min // 4) and cand_lt < rise_min)
|
||||||
|
micro_gap = (
|
||||||
|
st.is_recive
|
||||||
|
and not st.is_preamb
|
||||||
|
and cand_ht < (rise_min // 8)
|
||||||
|
and lt_ok
|
||||||
|
and cand_rp >= (rise_min // 4)
|
||||||
|
and cand_rp < rise_min
|
||||||
|
and cand_rp <= IR_TIMEOUT
|
||||||
|
)
|
||||||
|
if micro_gap:
|
||||||
|
st.err_other += 1
|
||||||
|
if GLITCH_REJECT_PHASE_NUDGE:
|
||||||
|
st.prev_rise = glitch_phase_nudge(t_us, rise_sync_time, st.prev_rise)
|
||||||
|
skip_rest = True
|
||||||
|
if not skip_rest and cand_rp <= rise_max / 4 and not st.high_count and not st.low_count:
|
||||||
|
st.err_other += 1
|
||||||
|
skip_rest = True
|
||||||
|
if not skip_rest:
|
||||||
|
# Как IR_DecoderRaw::tick: до обновления prev_rise, иначе (t_us - prev_rise) == 0 на подъёме.
|
||||||
|
if cand_rp > irmax * 2 and not st.is_recive_raw:
|
||||||
|
first_rx(st)
|
||||||
|
st.preamb_front_counter = PREAMB_FRONTS - 1
|
||||||
|
st.is_preamb = True
|
||||||
|
st.is_recive = True
|
||||||
|
st.is_recive_raw = True
|
||||||
|
st.is_wrong_pack = False
|
||||||
|
st.rise_period = cand_rp
|
||||||
|
st.high_time = cand_ht
|
||||||
|
st.low_time = cand_lt
|
||||||
|
st.prev_rise = t_us
|
||||||
|
else:
|
||||||
|
if t_us - st.prev_fall > rise_min / 4:
|
||||||
|
st.prev_fall = t_us
|
||||||
|
else:
|
||||||
|
skip_err = False
|
||||||
|
if IN_MARK_DOUBLE_FALL_IGNORE:
|
||||||
|
hi_since_rise = t_us - st.prev_rise
|
||||||
|
mark_end_min = (rise_min * BIT_ACTIVE_TAKTS) // (
|
||||||
|
BIT_ACTIVE_TAKTS + BIT_PAUSE_TAKTS
|
||||||
|
)
|
||||||
|
skip_err = (
|
||||||
|
st.is_recive
|
||||||
|
and not st.is_preamb
|
||||||
|
and hi_since_rise < mark_end_min
|
||||||
|
)
|
||||||
|
if not skip_err:
|
||||||
|
st.err_other += 1
|
||||||
|
|
||||||
|
if skip_rest:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Старт нового кадра после длинной паузы (в прошивке буфер обнуляется через available/таймаут;
|
||||||
|
# для мульти-пакета в симуляции явно first_rx, иначе i_data_buffer залипает).
|
||||||
|
if t_us > st.prev_rise and (t_us - st.prev_rise) > irmax * 2 and not st.is_recive_raw:
|
||||||
|
first_rx(st)
|
||||||
|
st.preamb_front_counter = PREAMB_FRONTS - 1
|
||||||
|
st.is_preamb = True
|
||||||
|
st.is_recive = True
|
||||||
|
st.is_recive_raw = True
|
||||||
|
st.is_wrong_pack = False
|
||||||
|
|
||||||
|
if st.preamb_front_counter:
|
||||||
|
if rising and st.rise_period < irmax:
|
||||||
|
if st.rise_period < rise_min // 2:
|
||||||
|
st.preamb_front_counter += 2
|
||||||
|
st.err_other += 1
|
||||||
|
st.preamb_front_counter -= 1
|
||||||
|
else:
|
||||||
|
if st.is_preamb:
|
||||||
|
st.is_preamb = False
|
||||||
|
st.prev_rise += st.rise_period / 2.0
|
||||||
|
return
|
||||||
|
|
||||||
|
if st.is_preamb:
|
||||||
|
return
|
||||||
|
|
||||||
|
if st.rise_period > irmax or st.is_buffer_overflow or st.rise_period < rise_min or st.is_wrong_pack:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not rising:
|
||||||
|
return
|
||||||
|
|
||||||
|
st.high_count = st.low_count = st.all_count = 0
|
||||||
|
invert_err = False
|
||||||
|
|
||||||
|
def write_to_buffer(bit: bool, invert_fix: bool) -> None:
|
||||||
|
if st.i_data_buffer > DATA_BYTE_SIZE_MAX * 8:
|
||||||
|
st.is_buffer_overflow = True
|
||||||
|
if st.is_buffer_overflow or st.is_preamb or st.is_wrong_pack:
|
||||||
|
st.is_recive = False
|
||||||
|
st.is_recive_raw = False
|
||||||
|
return
|
||||||
|
if st.buf_bit_pos == st.next_control_bit:
|
||||||
|
st.next_control_bit += SYNC_BITS if st.is_data else BIT_PER_BYTE
|
||||||
|
st.is_data = not st.is_data
|
||||||
|
st.i_sync_bit = 0
|
||||||
|
st.err_sync_bit = 0
|
||||||
|
if st.is_data:
|
||||||
|
bi = st.i_data_buffer
|
||||||
|
st.data_buffer[bi // 8] |= (1 if bit else 0) << (7 - (bi % 8))
|
||||||
|
st.i_data_buffer += 1
|
||||||
|
st.buf_bit_pos += 1
|
||||||
|
st.bits_log.append((t_us, "D", 1 if bit else 0))
|
||||||
|
else:
|
||||||
|
if st.i_sync_bit == 0:
|
||||||
|
last_b = (st.data_buffer[((st.i_data_buffer - 1) // 8)] >> (7 - ((st.i_data_buffer - 1) % 8))) & 1
|
||||||
|
if bit != bool(last_b):
|
||||||
|
st.buf_bit_pos += 1
|
||||||
|
st.i_sync_bit += 1
|
||||||
|
st.bits_log.append((t_us, "S", 1 if bit else 0))
|
||||||
|
else:
|
||||||
|
st.i_sync_bit = 0
|
||||||
|
st.err_other += 1
|
||||||
|
st.err_sync_bit += 1
|
||||||
|
if st.err_sync_bit >= SYNC_BITS:
|
||||||
|
st.is_wrong_pack = True
|
||||||
|
else:
|
||||||
|
st.buf_bit_pos += 1
|
||||||
|
st.i_sync_bit += 1
|
||||||
|
st.bits_log.append((t_us, "S", 1 if bit else 0))
|
||||||
|
st.is_wrong_pack = st.err_sync_bit >= SYNC_BITS
|
||||||
|
if st.is_data and not st.is_wrong_pack:
|
||||||
|
if st.i_data_buffer == 8 * MSG_BYTES:
|
||||||
|
st.pack_size = st.data_buffer[0] & IR_MASK_MSG_INFO
|
||||||
|
if st.pack_size and st.i_data_buffer == st.pack_size * BIT_PER_BYTE:
|
||||||
|
ok = crc_check(st.data_buffer, st.pack_size)
|
||||||
|
st.packets.append((ok, st.pack_size, bytes(st.data_buffer[: st.pack_size])))
|
||||||
|
st.is_recive = False
|
||||||
|
st.is_recive_raw = False
|
||||||
|
# буфер не чистят здесь — как в IR_DecoderRaw; firstRX по listenStart
|
||||||
|
|
||||||
|
if around_rise_period(st.rise_period, rise_sync_time):
|
||||||
|
if st.high_time > st.low_time:
|
||||||
|
write_to_buffer(True, False)
|
||||||
|
else:
|
||||||
|
write_to_buffer(False, False)
|
||||||
|
elif RISE_GRAY_SINGLE_BIT_FALLBACK and rise_gray_single_bit_fallback(st.rise_period, rise_sync_time):
|
||||||
|
st.err_other += 1
|
||||||
|
if st.high_time > st.low_time:
|
||||||
|
write_to_buffer(True, False)
|
||||||
|
else:
|
||||||
|
write_to_buffer(False, False)
|
||||||
|
else:
|
||||||
|
hc = ceil_div(min(st.high_time, 0xFFFF), rise_sync_time)
|
||||||
|
lc = ceil_div(min(st.low_time, 0xFFFF), rise_sync_time)
|
||||||
|
ac = ceil_div(min(st.rise_period, 0xFFFF), rise_sync_time)
|
||||||
|
st.high_count = min(hc, 127)
|
||||||
|
st.low_count = min(lc, 127)
|
||||||
|
st.all_count = min(ac, 127)
|
||||||
|
if st.high_count == 0 and st.high_time > rise_sync_time // 3:
|
||||||
|
st.high_count += 1
|
||||||
|
st.err_other += 1
|
||||||
|
if st.low_count + st.high_count > st.all_count:
|
||||||
|
if st.low_count > st.high_count:
|
||||||
|
st.low_count = st.all_count - st.high_count
|
||||||
|
st.err_low += st.low_count
|
||||||
|
elif st.low_count < st.high_count:
|
||||||
|
st.high_count = st.all_count - st.low_count
|
||||||
|
st.err_high += st.high_count
|
||||||
|
elif st.low_count == st.high_count:
|
||||||
|
invert_err = True
|
||||||
|
st.err_other += st.all_count
|
||||||
|
if st.low_count < st.high_count:
|
||||||
|
st.err_high += st.high_count
|
||||||
|
else:
|
||||||
|
st.err_low += st.low_count
|
||||||
|
# Как IR_DecoderRaw.cpp / IrFoxDecoder: не более 8 LOW и 8 HIGH за один подъём (i < n && 8 - i).
|
||||||
|
i = 0
|
||||||
|
while i < st.low_count and (8 - i):
|
||||||
|
if i == st.low_count - 1 and invert_err:
|
||||||
|
invert_err = False
|
||||||
|
write_to_buffer(True, True)
|
||||||
|
else:
|
||||||
|
write_to_buffer(False, False)
|
||||||
|
i += 1
|
||||||
|
i = 0
|
||||||
|
while i < st.high_count and (8 - i):
|
||||||
|
if i == st.high_count - 1 and invert_err:
|
||||||
|
invert_err = False
|
||||||
|
write_to_buffer(False, True)
|
||||||
|
else:
|
||||||
|
write_to_buffer(True, False)
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
if trace_rise is not None and rising and not skip_rest:
|
||||||
|
# После decode: залогировать только реальные записи битов (не преамбула, не ранний return выше)
|
||||||
|
if (
|
||||||
|
not st.is_preamb
|
||||||
|
and st.rise_period <= irmax
|
||||||
|
and not st.is_buffer_overflow
|
||||||
|
and st.rise_period >= rise_min
|
||||||
|
):
|
||||||
|
rec: dict = {
|
||||||
|
"t_us": t_us,
|
||||||
|
"rp": st.rise_period,
|
||||||
|
"ht": st.high_time,
|
||||||
|
"lt": st.low_time,
|
||||||
|
"i_buf": st.i_data_buffer,
|
||||||
|
"n_pkt": len(st.packets),
|
||||||
|
}
|
||||||
|
if around_rise_period(st.rise_period, rise_sync_time):
|
||||||
|
rec["branch"] = "around"
|
||||||
|
rec["bits_out"] = 1
|
||||||
|
elif RISE_GRAY_SINGLE_BIT_FALLBACK and rise_gray_single_bit_fallback(
|
||||||
|
st.rise_period, rise_sync_time
|
||||||
|
):
|
||||||
|
rec["branch"] = "gray"
|
||||||
|
rec["bits_out"] = 1
|
||||||
|
else:
|
||||||
|
rec["branch"] = "ceil"
|
||||||
|
rec["hc"] = st.high_count
|
||||||
|
rec["lc"] = st.low_count
|
||||||
|
rec["ac"] = st.all_count
|
||||||
|
rec["bits_out"] = st.low_count + st.high_count
|
||||||
|
trace_rise.append(rec)
|
||||||
|
|
||||||
|
|
||||||
|
def run_file(path: Path, max_packets: int = 0) -> SimState | None:
|
||||||
|
"""max_packets=0 — обработать весь файл (для регрессии по всем пакетам)."""
|
||||||
|
rows = parse_raw_csv(path)
|
||||||
|
if rows is None:
|
||||||
|
return None
|
||||||
|
edges = segments_to_edges(rows)
|
||||||
|
st = SimState()
|
||||||
|
rise_sync = BIT_TIME_US
|
||||||
|
for t_us, rising in edges:
|
||||||
|
tick(st, t_us, rising, rise_sync)
|
||||||
|
if max_packets and len(st.packets) >= max_packets:
|
||||||
|
break
|
||||||
|
return st
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
root = Path(__file__).resolve().parents[1]
|
||||||
|
default_set = [
|
||||||
|
("CAR", root / "Analyzer" / "raw" / "car_raw.txt"),
|
||||||
|
("POINT", root / "Analyzer" / "raw" / "point_raw.txt"),
|
||||||
|
("CARRAW3", root / "Analyzer" / "raw" / "carraw3.txt"),
|
||||||
|
("POINTRA3", root / "Analyzer" / "raw" / "pointraw_3.txt"),
|
||||||
|
]
|
||||||
|
files = default_set
|
||||||
|
if len(sys.argv) >= 2:
|
||||||
|
files = [(Path(p).name, Path(p)) for p in sys.argv[1:]]
|
||||||
|
|
||||||
|
for label, p in files:
|
||||||
|
if not p.exists():
|
||||||
|
print(f"skip {label}: {p} not found")
|
||||||
|
continue
|
||||||
|
st = run_file(p, max_packets=0)
|
||||||
|
print(f"=== {label} {p.name} ===")
|
||||||
|
if st is None:
|
||||||
|
print(
|
||||||
|
" (skip: decoder trace CSV Type/bit_idx, or missing Level+Duration; need Saleae level like car_raw.txt)"
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
n_ok = sum(1 for x in st.packets if x[0])
|
||||||
|
n_bad = len(st.packets) - n_ok
|
||||||
|
print(
|
||||||
|
f" packets={len(st.packets)} crc_ok={n_ok} crc_bad={n_bad} "
|
||||||
|
f"err_other={st.err_other} err_low={st.err_low} err_high={st.err_high}"
|
||||||
|
)
|
||||||
|
for i, (ok, psz, raw) in enumerate(st.packets[:12]):
|
||||||
|
hx = raw.hex(" ")
|
||||||
|
print(f" pkt[{i}] crc_ok={ok} len={psz} {hx}")
|
||||||
|
if len(st.packets) > 12:
|
||||||
|
print(f" ... ({len(st.packets) - 12} more packets)")
|
||||||
|
print(f" first 24 bits: {st.bits_log[:24]}")
|
||||||
|
|
||||||
|
car = root / "Analyzer" / "raw" / "car_raw.txt"
|
||||||
|
point = root / "Analyzer" / "raw" / "point_raw.txt"
|
||||||
|
if car.exists() and point.exists():
|
||||||
|
sc = run_file(car, max_packets=1)
|
||||||
|
sp = run_file(point, max_packets=1)
|
||||||
|
if sc is None or sp is None:
|
||||||
|
return
|
||||||
|
print("\n=== First packet bit diff (D/S sequence) CAR vs POINT ===")
|
||||||
|
for i, (a, b) in enumerate(zip(sc.bits_log, sp.bits_log)):
|
||||||
|
if a != b:
|
||||||
|
print(f" idx {i}: CAR {a} vs POINT {b}")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if len(sc.bits_log) != len(sp.bits_log):
|
||||||
|
print(f" len CAR={len(sc.bits_log)} POINT={len(sp.bits_log)}")
|
||||||
|
else:
|
||||||
|
print(" identical bit logs for min(len)")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user