feat: integrate tactile stream decoding
This commit is contained in:
125
CMakeLists.txt
125
CMakeLists.txt
@@ -1,94 +1,129 @@
|
|||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
project(touchsensor VERSION 2.0.0 LANGUAGES CXX)
|
project(touchsensor VERSION 2.0.0 LANGUAGES CXX)
|
||||||
|
|
||||||
set(BUILD_EXAMPLE ON)
|
option(BUILD_EXAMPLE "Build the cpstream demo executable" ON)
|
||||||
|
set(QT_VERSION Qt6 CACHE STRING "Qt major version to use")
|
||||||
|
|
||||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
||||||
set(CMAKE_CXX_STANDARD 23)
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||||
set(CMAKE_BUILD_TYPE "Release")
|
set(CMAKE_BUILD_TYPE "Release")
|
||||||
|
|
||||||
set(CMAKE_AUTOMOC ON)
|
set(CMAKE_AUTOMOC ON)
|
||||||
set(CMAKE_AUTORCC ON)
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|
||||||
add_compile_options(-Os -O3)
|
add_compile_options(-Os -O3)
|
||||||
add_subdirectory("serial")
|
|
||||||
|
|
||||||
set(QT_VERSION Qt6)
|
list(APPEND CMAKE_PREFIX_PATH
|
||||||
|
"D:/Environment/include"
|
||||||
|
"D:/Environment/lib"
|
||||||
|
)
|
||||||
|
|
||||||
find_package(${QT_VERSION} REQUIRED COMPONENTS Widgets Network PrintSupport)
|
find_package(${QT_VERSION} REQUIRED COMPONENTS Widgets Network PrintSupport)
|
||||||
find_package(Eigen3 REQUIRED)
|
find_package(Eigen3 REQUIRED)
|
||||||
|
|
||||||
include_directories(.)
|
qt_standard_project_setup()
|
||||||
|
|
||||||
file(
|
file(
|
||||||
GLOB_RECURSE PROJECT_SOURCE
|
GLOB_RECURSE MODERN_QT_SOURCES
|
||||||
CONFIGURE_DEPENDS
|
CONFIGURE_DEPENDS
|
||||||
# Project source
|
|
||||||
"modern-qt/*.cc"
|
"modern-qt/*.cc"
|
||||||
# Custom signals
|
|
||||||
"modern-qt/widget/sliders.hh"
|
|
||||||
)
|
)
|
||||||
add_library(
|
set(MODERN_QT_HEADERS
|
||||||
modern-qt SHARED
|
|
||||||
${PROJECT_SOURCE}
|
|
||||||
modern-qt/widget/select.hh
|
modern-qt/widget/select.hh
|
||||||
modern-qt/widget/select.impl.hh
|
modern-qt/widget/select.impl.hh
|
||||||
|
modern-qt/widget/sliders.hh
|
||||||
)
|
)
|
||||||
target_link_libraries(
|
add_library(modern-qt SHARED ${MODERN_QT_SOURCES} ${MODERN_QT_HEADERS})
|
||||||
modern-qt PUBLIC
|
target_include_directories(modern-qt PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
target_link_libraries(modern-qt
|
||||||
|
PUBLIC
|
||||||
${QT_VERSION}::Widgets
|
${QT_VERSION}::Widgets
|
||||||
${QT_VERSION}::Network
|
${QT_VERSION}::Network
|
||||||
|
Eigen3::Eigen
|
||||||
)
|
)
|
||||||
|
|
||||||
file(
|
file(
|
||||||
GLOB_RECURSE WIDGETS_CC
|
GLOB_RECURSE QCUSTOMPLOT_SOURCES
|
||||||
CONFIGURE_DEPENDS "components/*.cc"
|
|
||||||
)
|
|
||||||
|
|
||||||
file(
|
|
||||||
GLOB_RECURSE QCUSTOMPLOT_SOURCE
|
|
||||||
CONFIGURE_DEPENDS
|
CONFIGURE_DEPENDS
|
||||||
"qcustomplot/*.cpp"
|
"qcustomplot/*.cpp"
|
||||||
"qcustomplot/*.h"
|
"qcustomplot/*.h"
|
||||||
)
|
)
|
||||||
add_library(
|
add_library(qcustomplot SHARED ${QCUSTOMPLOT_SOURCES})
|
||||||
qcustomplot SHARED
|
target_include_directories(qcustomplot PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
${QCUSTOMPLOT_SOURCE}
|
target_link_libraries(qcustomplot
|
||||||
)
|
PUBLIC
|
||||||
target_link_libraries(
|
|
||||||
qcustomplot PUBLIC
|
|
||||||
${QT_VERSION}::Core
|
${QT_VERSION}::Core
|
||||||
${QT_VERSION}::Gui
|
${QT_VERSION}::Gui
|
||||||
${QT_VERSION}::PrintSupport
|
${QT_VERSION}::PrintSupport
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_standard_project_setup()
|
file(
|
||||||
add_executable(
|
GLOB_RECURSE COMPONENT_SOURCES
|
||||||
${PROJECT_NAME}
|
CONFIGURE_DEPENDS
|
||||||
${WIDGETS_CC}
|
"components/*.cc"
|
||||||
main.cc
|
)
|
||||||
|
file(
|
||||||
|
GLOB_RECURSE UTILITY_SOURCES
|
||||||
|
CONFIGURE_DEPENDS
|
||||||
|
"dlog/*.cc"
|
||||||
|
)
|
||||||
|
|
||||||
|
set(FFMSEP_SOURCES
|
||||||
|
components/ffmsep/cpdecoder.cc
|
||||||
|
components/ffmsep/cpstream_core.cc
|
||||||
|
components/ffmsep/tactile/tacdec.cc
|
||||||
|
)
|
||||||
|
set(FFMSEP_HEADERS
|
||||||
|
components/ffmsep/cpdecoder.hh
|
||||||
|
components/ffmsep/cpstream_core.hh
|
||||||
|
components/ffmsep/tactile/tacdec.hh
|
||||||
|
)
|
||||||
|
set(FFMSEP_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/components/ffmsep")
|
||||||
|
|
||||||
|
set(TOUCHSENSOR_HEADERS
|
||||||
component.hh
|
component.hh
|
||||||
resources.qrc
|
|
||||||
components/view.cc
|
|
||||||
modern-qt/widget/select.cc
|
|
||||||
components/charts/heatmap.cc
|
|
||||||
components/charts/heatmap.hh
|
components/charts/heatmap.hh
|
||||||
components/charts/heatmap.impl.hh
|
components/charts/heatmap.impl.hh
|
||||||
dlog/dlog.hh
|
dlog/dlog.hh
|
||||||
dlog/dlog.cc
|
${FFMSEP_HEADERS}
|
||||||
components/ffmsep/cpdecoder.hh
|
|
||||||
components/ffmsep/cpdecoder.cc
|
|
||||||
components/ffmsep/tactile/tacdec.h
|
|
||||||
components/ffmsep/tactile/tecdec.cc
|
|
||||||
)
|
)
|
||||||
qt6_add_resources(QRC_FILES resources.qrc)
|
|
||||||
target_sources(${PROJECT_NAME} PRIVATE ${QRC_FILES})
|
qt6_add_resources(APP_RESOURCES resources.qrc)
|
||||||
target_link_libraries(
|
|
||||||
${PROJECT_NAME}
|
add_executable(${PROJECT_NAME}
|
||||||
|
${COMPONENT_SOURCES}
|
||||||
|
${UTILITY_SOURCES}
|
||||||
|
${TOUCHSENSOR_HEADERS}
|
||||||
|
main.cc
|
||||||
|
)
|
||||||
|
target_sources(${PROJECT_NAME} PRIVATE ${APP_RESOURCES})
|
||||||
|
target_include_directories(${PROJECT_NAME}
|
||||||
|
PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${FFMSEP_INCLUDE_DIR}
|
||||||
|
)
|
||||||
|
target_link_libraries(${PROJECT_NAME}
|
||||||
|
PRIVATE
|
||||||
${QT_VERSION}::Widgets
|
${QT_VERSION}::Widgets
|
||||||
${QT_VERSION}::Network
|
${QT_VERSION}::Network
|
||||||
modern-qt
|
modern-qt
|
||||||
qcustomplot
|
qcustomplot
|
||||||
serial
|
serial
|
||||||
|
setupapi
|
||||||
spdlog
|
spdlog
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if(BUILD_EXAMPLE)
|
||||||
|
add_executable(cpstream_demo
|
||||||
|
examples/cpstream_demo.cc
|
||||||
|
${FFMSEP_SOURCES}
|
||||||
|
)
|
||||||
|
target_include_directories(cpstream_demo
|
||||||
|
PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}
|
||||||
|
${FFMSEP_INCLUDE_DIR}
|
||||||
|
)
|
||||||
|
target_link_libraries(cpstream_demo PRIVATE serial)
|
||||||
|
target_link_libraries(cpstream_demo PRIVATE setupapi)
|
||||||
|
endif()
|
||||||
|
|||||||
93
README.md
Normal file
93
README.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# TouchSensor 2.0
|
||||||
|
|
||||||
|
> Real-time tactile sensor exploration UI powered by Qt 6 and a custom Modern Qt component toolkit.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Highlights
|
||||||
|
- Modern, material-inspired desktop shell built on the in-repo `modern-qt` library.
|
||||||
|
- Modular component system (`NavComponent`, `ViewComponent`) for quick UI experiments.
|
||||||
|
- Real-time tactile matrix visualisation via the `HeatMapPlot` wrapper around QCustomPlot.
|
||||||
|
- Codec infrastructure (`components/ffmsep`) ready for custom tactile packet decoding.
|
||||||
|
- Serial transport module scaffolded for COM port discovery and streaming.
|
||||||
|
|
||||||
|
## Architecture At A Glance
|
||||||
|
- **Entry point**: `main.cc` composes the themed window, navigation rail, and card-based layout.
|
||||||
|
- **Components**: `components/` hosts UI widgets, charts, and the tactile decoder pipeline.
|
||||||
|
- **Modern Qt toolkit**: `modern-qt/` provides declarative wrappers, theming, and Material icon helpers.
|
||||||
|
- **Data layer**: `components/ffmsep` implements codec registration, packet decoding, and tactile frame processing.
|
||||||
|
- **Visualisation**: `components/charts/heatmap.*` exposes a themable heatmap control for sensor grids.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
- CMake 3.20+
|
||||||
|
- A C++23-capable compiler (MSVC 19.3x, Clang 16+, or GCC 13+)
|
||||||
|
- Qt 6 (Widgets, Network, PrintSupport modules)
|
||||||
|
- Eigen3
|
||||||
|
- `spdlog` (fetched via package manager or provided to CMake)
|
||||||
|
|
||||||
|
Ensure `Qt6_DIR` (or `CMAKE_PREFIX_PATH`) points to the Qt install so CMake can locate the required modules.
|
||||||
|
|
||||||
|
### Configure & Build
|
||||||
|
|
||||||
|

|
||||||
|
```powershell
|
||||||
|
pacman -Sy
|
||||||
|
pacman -S mingw-w64-x86_64-toolchain
|
||||||
|
pacman -S mingw64/mingw-w64-x86_64-qt6-base
|
||||||
|
pacman -S mingw-w64-x86_64-eigen3 mingw-w64-x86_64-yaml-cpp
|
||||||
|
|
||||||
|
mkdir build
|
||||||
|
cmake -G "MinGW Makefiles" -B build -DCMAKE_INSTALL_PREFIX="YOUR INSTALL PATH"
|
||||||
|
|
||||||
|
cd build && mingw32-make install
|
||||||
|
|
||||||
|
cat install_manifest.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
To run from the build directory:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\touchsensor.exe
|
||||||
|
```
|
||||||
|
|
||||||
|
On Linux/macOS adjust the Qt path and executable name accordingly.
|
||||||
|
|
||||||
|
## Project Layout
|
||||||
|
|
||||||
|
```text
|
||||||
|
.
|
||||||
|
|-- components/
|
||||||
|
| |-- charts/ # QCustomPlot-based visualisations (heatmaps, etc.)
|
||||||
|
| |-- ffmsep/ # Codec system and tactile decoder experiments
|
||||||
|
| |-- view.cc # Main dashboard composition
|
||||||
|
| `-- ... # Additional UI widgets
|
||||||
|
|-- modern-qt/ # In-house declarative Qt UI framework
|
||||||
|
|-- serial/ # Serial communication helper library
|
||||||
|
|-- images/logo.png # Current app branding
|
||||||
|
|-- main.cc # Application bootstrap
|
||||||
|
`-- CMakeLists.txt # Build script (adds Qt, Eigen, Modern Qt, Serial, SPDLOG)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Roadmap (WIP)
|
||||||
|
- Flesh out tactile codec implementations and connect them to live serial streams.
|
||||||
|
- Replace placeholder random data with decoded sensor frames.
|
||||||
|
- Expand navigation targets beyond the current demo cards.
|
||||||
|
- Capture screenshots or recordings for documentation.
|
||||||
|
- Polish theming, animation masks, and landing experience.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
This repository is in active development; feel free to open issues or PRs once guidelines land. Until then, keep discussions in the project chat or issues board.
|
||||||
|
|
||||||
|
## Acknowledgements
|
||||||
|
- [Qt](https://www.qt.io/) for the core UI framework.
|
||||||
|
- [QCustomPlot](https://www.qcustomplot.com/) powering the heatmap widget.
|
||||||
|
- [spdlog](https://github.com/gabime/spdlog) for logging (wired via CMake).
|
||||||
|
- Internal **Modern Qt** toolkit built on top of creeper-qt utilities.
|
||||||
@@ -5,12 +5,8 @@
|
|||||||
#include "heatmap.hh"
|
#include "heatmap.hh"
|
||||||
#include "heatmap.impl.hh"
|
#include "heatmap.impl.hh"
|
||||||
#include "qcustomplot/qcustomplot.h"
|
#include "qcustomplot/qcustomplot.h"
|
||||||
#include <qcolor.h>
|
|
||||||
#include <qcolormap.h>
|
|
||||||
#include <qcontainerfwd.h>
|
#include <qcontainerfwd.h>
|
||||||
#include <qdebug.h>
|
|
||||||
#include <qnamespace.h>
|
#include <qnamespace.h>
|
||||||
#include <qobject.h>
|
|
||||||
#include <qsize.h>
|
#include <qsize.h>
|
||||||
|
|
||||||
using namespace creeper::plot_widget::internal;
|
using namespace creeper::plot_widget::internal;
|
||||||
@@ -21,32 +17,30 @@ BasicPlot::BasicPlot() : pimpl{std::make_unique<Impl>(*this)} {
|
|||||||
setBackground(Qt::transparent);
|
setBackground(Qt::transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
BasicPlot::~BasicPlot() {}
|
BasicPlot::~BasicPlot() = default;
|
||||||
|
|
||||||
void BasicPlot::set_matrix_size(const QSize& size) {
|
void BasicPlot::set_matrix_size(const QSize& size)const {
|
||||||
qDebug() << "set matrix size" << size;
|
|
||||||
pimpl->set_matrix_size(size);
|
pimpl->set_matrix_size(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasicPlot::set_matrix_size(const int& w, const int& h) {
|
void BasicPlot::set_matrix_size(const int& w, const int& h)const {
|
||||||
QSize size(w, h);
|
QSize size(w, h);
|
||||||
qDebug() << "set matrix size" << size;
|
|
||||||
pimpl->set_matrix_size(size);
|
pimpl->set_matrix_size(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasicPlot::set_color_gradient_range(const double& min, const double& max) {
|
void BasicPlot::set_color_gradient_range(const double& min, const double& max)const {
|
||||||
pimpl->set_color_gradient_range(min, max);
|
pimpl->set_color_gradient_range(min, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasicPlot::set_xlabel_text(const QString& text) {
|
void BasicPlot::set_xlabel_text(const QString& text)const {
|
||||||
pimpl->set_xlabel_text(text);
|
pimpl->set_xlabel_text(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasicPlot::set_ylabel_text(const QString& text) {
|
void BasicPlot::set_ylabel_text(const QString& text)const {
|
||||||
pimpl->set_ylabel_text(text);
|
pimpl->set_ylabel_text(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasicPlot::load_theme_manager(ThemeManager& mgr) {
|
void BasicPlot::load_theme_manager(ThemeManager& mgr)const {
|
||||||
pimpl->load_theme_manager(mgr);
|
pimpl->load_theme_manager(mgr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,8 +48,7 @@ QSize BasicPlot::get_matrix_size() const {
|
|||||||
return pimpl->get_matrix_size();
|
return pimpl->get_matrix_size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasicPlot::set_data(const QVector<PointData>& data) {
|
void BasicPlot::set_data(const QVector<PointData>& data)const {
|
||||||
qDebug() << "set data" << data.size();
|
|
||||||
pimpl->set_data(data);
|
pimpl->set_data(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +56,7 @@ bool BasicPlot::is_initialized() const {
|
|||||||
return pimpl->is_plot_initialized();
|
return pimpl->is_plot_initialized();
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasicPlot::init_plot() {
|
void BasicPlot::init_plot()const {
|
||||||
pimpl->initialize_plot();
|
pimpl->initialize_plot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,17 +68,17 @@ void BasicPlot::paintEvent(QPaintEvent* event) {
|
|||||||
QCustomPlot::paintEvent(event);
|
QCustomPlot::paintEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasicPlot::dataChanged(const QVector<PointData>& map) {
|
void BasicPlot::dataChanged(const QVector<PointData>& map)const {
|
||||||
set_data(map);
|
set_data(map);
|
||||||
// emit dataChanged(map);
|
// emit dataChanged(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasicPlot::dataRangeChanged(const double& min, const double& max) {
|
void BasicPlot::dataRangeChanged(const double& min, const double& max)const {
|
||||||
set_color_gradient_range(min, max);
|
set_color_gradient_range(min, max);
|
||||||
// emit dataRangeChanged(min, max);
|
// emit dataRangeChanged(min, max);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BasicPlot::update_dynamic_heatmap(const QVector<PointData>& map) {
|
void BasicPlot::update_dynamic_heatmap(const QVector<PointData>& map)const {
|
||||||
set_data(map);
|
set_data(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,21 +35,21 @@ public:
|
|||||||
// BasicPlot(const BasicPlot&) = delete;
|
// BasicPlot(const BasicPlot&) = delete;
|
||||||
// BasicPlot& operator=(const BasicPlot&) = delete;
|
// BasicPlot& operator=(const BasicPlot&) = delete;
|
||||||
|
|
||||||
void init_plot();
|
void init_plot()const;
|
||||||
void load_theme_manager(ThemeManager&);
|
void load_theme_manager(ThemeManager&)const;
|
||||||
void set_xlabel_text(const QString&);
|
void set_xlabel_text(const QString&)const;
|
||||||
void set_ylabel_text(const QString&);
|
void set_ylabel_text(const QString&)const;
|
||||||
void set_matrix_size(const QSize&);
|
void set_matrix_size(const QSize&)const;
|
||||||
void set_matrix_size(const int& w, const int& h);
|
void set_matrix_size(const int& w, const int& h)const;
|
||||||
void set_data(const QVector<PointData>& data);
|
void set_data(const QVector<PointData>& data)const;
|
||||||
void set_color_gradient_range(const double& min, const double& max);
|
void set_color_gradient_range(const double& min, const double& max)const;
|
||||||
QSize get_matrix_size() const;
|
QSize get_matrix_size() const;
|
||||||
bool is_initialized() const;
|
bool is_initialized() const;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void update_dynamic_heatmap(const QVector<PointData>& map);
|
void update_dynamic_heatmap(const QVector<PointData>& map)const;
|
||||||
void dataChanged(const QVector<PointData>& map);
|
void dataChanged(const QVector<PointData>& map)const;
|
||||||
void dataRangeChanged(const double& min, const double& max);
|
void dataRangeChanged(const double& min, const double& max)const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent*) override;
|
void paintEvent(QPaintEvent*) override;
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ public:
|
|||||||
|
|
||||||
// 创建颜色映射
|
// 创建颜色映射
|
||||||
QCPColorMap* cpmp = new QCPColorMap(self.xAxis, self.yAxis);
|
QCPColorMap* cpmp = new QCPColorMap(self.xAxis, self.yAxis);
|
||||||
qDebug() << "initialize_plot() size:" << matrix_size;
|
|
||||||
cpmp->data()->setSize(matrix_size.width(), matrix_size.height());
|
cpmp->data()->setSize(matrix_size.width(), matrix_size.height());
|
||||||
cpmp->data()->setRange(QCPRange(0.5, matrix_size.width() - 0.5),
|
cpmp->data()->setRange(QCPRange(0.5, matrix_size.width() - 0.5),
|
||||||
QCPRange(0.5, matrix_size.height() - 0.5));
|
QCPRange(0.5, matrix_size.height() - 0.5));
|
||||||
|
|||||||
@@ -1,226 +0,0 @@
|
|||||||
//
|
|
||||||
// FFmpeg 风格编解码器注册表与上下文管理实现(中文注释版)。
|
|
||||||
// 注意:此实现大量使用 std::vector、std::optional 等现代 C++ 容器与 RAII 思想。
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "cpdecoder.hh"
|
|
||||||
|
|
||||||
#include <algorithm> // 提供 std::find_if 等算法,用于查找与遍历
|
|
||||||
#include <mutex> // std::mutex + std::lock_guard,保证注册表线程安全
|
|
||||||
|
|
||||||
namespace ffmsep {
|
|
||||||
|
|
||||||
namespace { // 匿名命名空间:限制辅助函数和静态变量作用域在当前编译单元
|
|
||||||
|
|
||||||
std::vector<const CPCodec*>& codec_registry() {
|
|
||||||
static std::vector<const CPCodec*> registry; // 使用静态局部变量 + vector 保留所有注册的编解码器
|
|
||||||
return registry;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::mutex& registry_mutex() {
|
|
||||||
static std::mutex m; // 构建一个全局互斥量,用于守护注册表操作
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
void attach_codec(CPCodecContext* ctx, const CPCodec* codec) {
|
|
||||||
if (!ctx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx->codec = codec;
|
|
||||||
if (!codec) {
|
|
||||||
ctx->codec_type = CPMediaType::Unknown;
|
|
||||||
ctx->priv_data = nullptr;
|
|
||||||
ctx->release_priv_storage();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx->codec_type = codec->type;
|
|
||||||
ctx->priv_data = ctx->ensure_priv_storage(codec->priv_data_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool codec_name_equals(const CPCodec* codec, std::string_view name) {
|
|
||||||
if (!codec || !codec->name) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return std::string_view(codec->name) == name; // std::string_view 避免额外拷贝
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
void* CPCodecContext::ensure_priv_storage(std::size_t size) {
|
|
||||||
// 将 vector 当作动态缓冲区,确保大小满足编解码器私有数据需求
|
|
||||||
if (size == 0U) {
|
|
||||||
priv_storage.clear();
|
|
||||||
priv_data = nullptr;
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if (priv_storage.size() != size) {
|
|
||||||
priv_storage.assign(size, static_cast<std::uint8_t>(0));
|
|
||||||
}
|
|
||||||
priv_data = priv_storage.data();
|
|
||||||
return priv_data;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CPCodecContext::release_priv_storage() noexcept {
|
|
||||||
// 与 ensure 配套,释放时直接清空 vector
|
|
||||||
priv_storage.clear();
|
|
||||||
priv_data = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将单个编解码器指针注册到全局列表,重复注册时作安全检查。
|
|
||||||
void cpcodec_register(const CPCodec* codec) {
|
|
||||||
if (!codec || !codec->name) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(registry_mutex());
|
|
||||||
auto& reg = codec_registry();
|
|
||||||
auto already = std::find(reg.begin(), reg.end(), codec);
|
|
||||||
if (already != reg.end()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto same_id = std::find_if(reg.begin(), reg.end(), [codec](const CPCodec* entry) {
|
|
||||||
return entry && codec && entry->id == codec->id && codec->id != CPCodecID::Unknown;
|
|
||||||
});
|
|
||||||
if (same_id != reg.end()) {
|
|
||||||
*same_id = codec; // 如果 ID 已存在,则替换为新实现,以最新注册者为准
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
reg.push_back(codec);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 批量注册函数:利用 C++11 std::initializer_list 便捷传入多个指针。
|
|
||||||
void cpcodec_register_many(std::initializer_list<const CPCodec*> codecs) {
|
|
||||||
for (const CPCodec* codec : codecs) {
|
|
||||||
cpcodec_register(codec);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const CPCodec* cpcodec_find_decoder(CPCodecID id) {
|
|
||||||
std::lock_guard<std::mutex> lock(registry_mutex());
|
|
||||||
const auto& reg = codec_registry();
|
|
||||||
auto it = std::find_if(reg.begin(), reg.end(), [id](const CPCodec* codec) {
|
|
||||||
return codec && codec->id == id;
|
|
||||||
});
|
|
||||||
return it == reg.end() ? nullptr : *it; // 使用可空返回值:找不到时返回 nullptr
|
|
||||||
}
|
|
||||||
|
|
||||||
const CPCodec* cpcodec_find_decoder_by_name(std::string_view name) {
|
|
||||||
std::lock_guard<std::mutex> lock(registry_mutex());
|
|
||||||
const auto& reg = codec_registry();
|
|
||||||
auto it = std::find_if(reg.begin(), reg.end(), [name](const CPCodec* codec) {
|
|
||||||
return codec_name_equals(codec, name);
|
|
||||||
});
|
|
||||||
return it == reg.end() ? nullptr : *it;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<const CPCodec*> cpcodec_list_codecs() {
|
|
||||||
std::lock_guard<std::mutex> lock(registry_mutex());
|
|
||||||
return codec_registry(); // 返回当前注册表的浅拷贝
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分配一个新的上下文,并可选绑定已有编解码器。
|
|
||||||
CPCodecContext* cpcodec_alloc_context3(const CPCodec* codec) {
|
|
||||||
auto* ctx = new CPCodecContext();
|
|
||||||
if (codec) {
|
|
||||||
attach_codec(ctx, codec);
|
|
||||||
}
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 打开上下文:允许调用方在此时指定(或替换)具体编解码器。
|
|
||||||
int cpcodec_open2(CPCodecContext* ctx, const CPCodec* codec) {
|
|
||||||
if (!ctx) {
|
|
||||||
return CP_ERROR_INVALID_ARGUMENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ctx->is_open) {
|
|
||||||
return CP_ERROR_INVALID_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (codec) {
|
|
||||||
attach_codec(ctx, codec);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ctx->codec) {
|
|
||||||
return CP_ERROR_INVALID_ARGUMENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx->is_open = true;
|
|
||||||
if (ctx->codec->init) {
|
|
||||||
int rc = ctx->codec->init(ctx);
|
|
||||||
if (rc < 0) {
|
|
||||||
ctx->is_open = false;
|
|
||||||
if (ctx->codec->close) {
|
|
||||||
ctx->codec->close(ctx); // 初始化失败时调用 close 进行善后
|
|
||||||
}
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return CP_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 关闭上下文:调用编解码器收尾逻辑并释放私有状态。
|
|
||||||
int cpcodec_close(CPCodecContext* ctx) {
|
|
||||||
if (!ctx) {
|
|
||||||
return CP_ERROR_INVALID_ARGUMENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ctx->is_open) {
|
|
||||||
return CP_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ctx->codec && ctx->codec->close) {
|
|
||||||
ctx->codec->close(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx->is_open = false;
|
|
||||||
ctx->release_priv_storage();
|
|
||||||
ctx->codec_type = CPMediaType::Unknown;
|
|
||||||
ctx->codec = nullptr;
|
|
||||||
ctx->priv_data = nullptr;
|
|
||||||
return CP_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 释放上下文指针,防止悬挂引用。
|
|
||||||
void cpcodec_free_context(CPCodecContext** ctx) {
|
|
||||||
if (!ctx || !*ctx) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
cpcodec_close(*ctx);
|
|
||||||
delete *ctx;
|
|
||||||
*ctx = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 输入侧 API:推送一帧串口数据,内部队列通常会缓冲。
|
|
||||||
int cpcodec_send_packet(CPCodecContext* ctx, const CPPacket* packet) {
|
|
||||||
if (!ctx || !packet) {
|
|
||||||
return CP_ERROR_INVALID_ARGUMENT;
|
|
||||||
}
|
|
||||||
if (!ctx->is_open || !ctx->codec) {
|
|
||||||
return CP_ERROR_NOT_OPEN;
|
|
||||||
}
|
|
||||||
if (!ctx->codec->send_packet) {
|
|
||||||
return CP_ERROR_INVALID_STATE;
|
|
||||||
}
|
|
||||||
return ctx->codec->send_packet(ctx, *packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 输出侧 API:尝试从编解码器读取一帧解码结果。
|
|
||||||
int cpcodec_receive_frame(CPCodecContext* ctx, CPFrame* frame) {
|
|
||||||
if (!ctx || !frame) {
|
|
||||||
return CP_ERROR_INVALID_ARGUMENT;
|
|
||||||
}
|
|
||||||
if (!ctx->is_open || !ctx->codec) {
|
|
||||||
return CP_ERROR_NOT_OPEN;
|
|
||||||
}
|
|
||||||
if (!ctx->codec->receive_frame) {
|
|
||||||
return CP_ERROR_INVALID_STATE;
|
|
||||||
}
|
|
||||||
return ctx->codec->receive_frame(ctx, *frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ffmsep
|
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
//
|
|
||||||
// FFmpeg 风格的串口解码工具核心定义(带详细中文注释)。
|
|
||||||
// 说明:本文件使用了 C++17 引入的 std::optional(可选值容器)与 [[nodiscard]] 属性,
|
|
||||||
// 以及 C++11 的 std::initializer_list、基于 enum class 的强类型枚举,用于提高类型安全。
|
|
||||||
//
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint> // 固定宽度整数类型,便于协议字段与位宽一致
|
|
||||||
#include <cstddef>
|
|
||||||
#include <mutex> // 互斥锁,用于注册表线程安全
|
|
||||||
#include <optional> // C++17 可选类型,用来表示可能为空的查找结果
|
|
||||||
#include <string>
|
|
||||||
#include <string_view> // C++17 字符串视图,避免不必要的拷贝
|
|
||||||
#include <vector>
|
|
||||||
#include <initializer_list> // C++11 可初始化列表,支持批量注册编解码器
|
|
||||||
|
|
||||||
namespace ffmsep { // 命名空间隔离所有与串口编解码相关的类型
|
|
||||||
|
|
||||||
// 错误码定义:参考 FFmpeg 的返回值约定,所有负数表示异常。
|
|
||||||
inline constexpr int CP_SUCCESS = 0; // 成功
|
|
||||||
inline constexpr int CP_ERROR_EOF = -1; // 流结束(End Of File)
|
|
||||||
inline constexpr int CP_ERROR_EAGAIN = -2; // 数据暂不可用,稍后重试
|
|
||||||
inline constexpr int CP_ERROR_NOT_OPEN = -3; // 编解码上下文尚未打开
|
|
||||||
inline constexpr int CP_ERROR_INVALID_STATE = -4; // 当前状态不允许该操作
|
|
||||||
inline constexpr int CP_ERROR_INVALID_ARGUMENT = -5;// 传入参数不合法
|
|
||||||
|
|
||||||
// enum class 使用 C++11 强类型枚举,避免隐式转换导致的错误。
|
|
||||||
enum class CPMediaType : std::uint8_t {
|
|
||||||
Unknown = 0, // 未知类型:默认值
|
|
||||||
Data, // 数据流(例如串口数据)
|
|
||||||
Audio, // 音频流(预留扩展)
|
|
||||||
Video // 视频流(预留扩展)
|
|
||||||
};
|
|
||||||
|
|
||||||
// 编解码器 ID,用于在注册表中快速定位具体实现。
|
|
||||||
enum class CPCodecID : std::uint32_t {
|
|
||||||
Unknown = 0, // 未知或未设置
|
|
||||||
Tactile = 0x54514354u // 'T','Q','C','T':触觉传感器协议标识
|
|
||||||
};
|
|
||||||
|
|
||||||
// CPPacket 表示输入的“帧包”,与 FFmpeg 中 AVPacket 的思想类似。
|
|
||||||
struct CPPacket {
|
|
||||||
std::vector<std::uint8_t> payload; // 有效载荷:串口原始数据
|
|
||||||
std::int64_t pts = 0; // 显示时间戳(presentation timestamp)
|
|
||||||
std::int64_t dts = 0; // 解码时间戳(decode timestamp)
|
|
||||||
bool end_of_stream = false; // 标记此包是否为流结尾
|
|
||||||
bool flush = false; // 是否请求刷新内部状态(例如重置缓冲)
|
|
||||||
|
|
||||||
CPPacket() = default;
|
|
||||||
CPPacket(std::vector<std::uint8_t> data, std::int64_t pts_value = 0, std::int64_t dts_value = 0) noexcept
|
|
||||||
: payload(std::move(data)), pts(pts_value), dts(dts_value) {}
|
|
||||||
|
|
||||||
[[nodiscard]] bool empty() const noexcept { return payload.empty(); } // [[nodiscard]] 防止忽略返回值
|
|
||||||
};
|
|
||||||
|
|
||||||
// CPFrame 表示解码后的数据帧,对应业务层可消费的实体。
|
|
||||||
struct CPFrame {
|
|
||||||
std::vector<std::uint8_t> data; // 解码后的数据内容
|
|
||||||
std::int64_t pts = 0; // 与输入包对应的时间戳
|
|
||||||
bool key_frame = false; // 是否为关键帧(例如起始帧)
|
|
||||||
bool valid = false; // 是否包含有效数据
|
|
||||||
|
|
||||||
void reset() noexcept {
|
|
||||||
data.clear();
|
|
||||||
pts = 0;
|
|
||||||
key_frame = false;
|
|
||||||
valid = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CPCodecContext;
|
|
||||||
|
|
||||||
// CPCodec 用函数指针描述具体编解码器的行为,等价于 FFmpeg 中的 AVCodec。
|
|
||||||
struct CPCodec {
|
|
||||||
using InitFn = int (*)(CPCodecContext*); // 初始化回调
|
|
||||||
using CloseFn = void (*)(CPCodecContext*); // 关闭回调
|
|
||||||
using SendPacketFn = int (*)(CPCodecContext*, const CPPacket&); // 发送输入包
|
|
||||||
using ReceiveFrameFn = int (*)(CPCodecContext*, CPFrame&); // 拉取输出帧
|
|
||||||
|
|
||||||
const char* name = nullptr;
|
|
||||||
const char* long_name = nullptr;
|
|
||||||
CPMediaType type = CPMediaType::Unknown;
|
|
||||||
CPCodecID id = CPCodecID::Unknown;
|
|
||||||
std::size_t priv_data_size = 0;
|
|
||||||
InitFn init = nullptr;
|
|
||||||
CloseFn close = nullptr;
|
|
||||||
SendPacketFn send_packet = nullptr;
|
|
||||||
ReceiveFrameFn receive_frame = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CPCodecContext {
|
|
||||||
const CPCodec* codec = nullptr; // 指向当前使用的编解码器描述
|
|
||||||
void* priv_data = nullptr; // 指向编解码器私有状态(大小由 priv_data_size 控制)
|
|
||||||
CPMediaType codec_type = CPMediaType::Unknown; // 保存媒体类型,便于外部查询
|
|
||||||
bool is_open = false; // 是否已经成功调用 open
|
|
||||||
|
|
||||||
void clear() noexcept {
|
|
||||||
codec = nullptr;
|
|
||||||
priv_data = nullptr;
|
|
||||||
codec_type = CPMediaType::Unknown;
|
|
||||||
is_open = false;
|
|
||||||
priv_storage.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void* ensure_priv_storage(std::size_t size); // 确保私有存储空间足够,不足时重新分配
|
|
||||||
void release_priv_storage() noexcept; // 释放私有存储
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
[[nodiscard]] T* priv_as() noexcept {
|
|
||||||
return static_cast<T*>(priv_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
[[nodiscard]] const T* priv_as() const noexcept {
|
|
||||||
return static_cast<const T*>(priv_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::vector<std::uint8_t> priv_storage; // 私有缓冲区,使用 std::vector 管理生命周期
|
|
||||||
|
|
||||||
friend CPCodecContext* cpcodec_alloc_context3(const CPCodec*);
|
|
||||||
friend int cpcodec_open2(CPCodecContext*, const CPCodec*);
|
|
||||||
friend int cpcodec_close(CPCodecContext*);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 注册接口:允许外部模块将编解码器加入全局列表。
|
|
||||||
void cpcodec_register(const CPCodec* codec);
|
|
||||||
void cpcodec_register_many(std::initializer_list<const CPCodec*> codecs);
|
|
||||||
const CPCodec* cpcodec_find_decoder(CPCodecID id);
|
|
||||||
const CPCodec* cpcodec_find_decoder_by_name(std::string_view name);
|
|
||||||
std::vector<const CPCodec*> cpcodec_list_codecs();
|
|
||||||
|
|
||||||
CPCodecContext* cpcodec_alloc_context3(const CPCodec* codec); // 分配上下文,关联指定编解码器
|
|
||||||
int cpcodec_open2(CPCodecContext* ctx, const CPCodec* codec = nullptr); // 打开上下文,可在此处指定或替换编解码器
|
|
||||||
int cpcodec_close(CPCodecContext* ctx); // 关闭上下文并释放资源
|
|
||||||
void cpcodec_free_context(CPCodecContext** ctx); // 释放上下文指针
|
|
||||||
int cpcodec_send_packet(CPCodecContext* ctx, const CPPacket* packet); // 推送一份待解码的数据包
|
|
||||||
int cpcodec_receive_frame(CPCodecContext* ctx, CPFrame* frame); // 拉取解码出来的帧
|
|
||||||
|
|
||||||
} // namespace ffmsep
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
//
|
|
||||||
// 触觉传感器串口协议高层解析工具(中文注释版)。
|
|
||||||
// 注意:文件使用了 C++17 的 std::optional 与 inline constexpr,
|
|
||||||
// 可提供编译期常量与安全的可空返回值。
|
|
||||||
//
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "../cpdecoder.hh"
|
|
||||||
|
|
||||||
#include <cstdint> // 协议字段均以固定宽度字节表示
|
|
||||||
#include <optional> // std::optional:可能解析失败时返回空
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace ffmsep::tactile {
|
|
||||||
|
|
||||||
inline constexpr std::uint8_t kStartByte = 0x3A; // 帧起始符(冒号)
|
|
||||||
inline constexpr std::uint8_t kEndByteFirst = 0x0D; // 帧结束符(回车)
|
|
||||||
inline constexpr std::uint8_t kEndByteSecond = 0x0A; // 帧结束符(换行)
|
|
||||||
|
|
||||||
// 功能码枚举:使用 enum class 提升类型安全,避免与其他数值混用。
|
|
||||||
enum class FunctionCode : std::uint8_t {
|
|
||||||
Unknown = 0x00, // 未知功能,解析失败或未初始化
|
|
||||||
ReadMatrix = 0x01, // 读取整块矩阵 AD 值
|
|
||||||
ReadSingle = 0x02, // 读取单点 AD 值
|
|
||||||
ReadTemperature = 0x03,// 读取温度数据
|
|
||||||
SetDeviceId = 0x51, // 修改设备编号
|
|
||||||
SetMatrixSize = 0x52, // 修改矩阵尺寸(长边/短边)
|
|
||||||
CalibrationMode = 0x53 // 进入校准模式
|
|
||||||
};
|
|
||||||
|
|
||||||
struct MatrixSize {
|
|
||||||
std::uint8_t long_edge = 0; // 矩阵长边尺寸
|
|
||||||
std::uint8_t short_edge = 0; // 矩阵短边尺寸
|
|
||||||
};
|
|
||||||
|
|
||||||
struct TactileFrame {
|
|
||||||
std::uint8_t device_address = 0; // 设备地址(1~255)
|
|
||||||
FunctionCode function = FunctionCode::Unknown; // 功能码
|
|
||||||
std::uint8_t data_length = 0; // 数据域长度
|
|
||||||
std::vector<std::uint8_t> payload; // 数据域内容(按协议解析)
|
|
||||||
};
|
|
||||||
|
|
||||||
// 将底层 CPFrame 解析为结构化的 TactileFrame。
|
|
||||||
std::optional<TactileFrame> parse_frame(const CPFrame& frame);
|
|
||||||
// 将数据域按小端 16 位压力值数组解析。
|
|
||||||
std::vector<std::uint16_t> parse_pressure_values(const TactileFrame& frame);
|
|
||||||
// 解析矩阵尺寸(用于读/写矩阵尺寸的命令)。
|
|
||||||
std::optional<MatrixSize> parse_matrix_size_payload(const TactileFrame& frame);
|
|
||||||
// 解析矩阵坐标(复用尺寸结构,分别表示长边/短边索引)。
|
|
||||||
std::optional<MatrixSize> parse_matrix_coordinate_payload(const TactileFrame& frame);
|
|
||||||
|
|
||||||
const CPCodec* tactile_codec();
|
|
||||||
void register_tactile_codec();
|
|
||||||
|
|
||||||
} // namespace ffmsep::tactile
|
|
||||||
@@ -1,297 +0,0 @@
|
|||||||
//
|
|
||||||
// 触觉传感器串口协议解码器实现(中文注释版)。
|
|
||||||
// 使用要点:
|
|
||||||
// 1. 通过循环缓冲累积串口字节,按起始/结束符重组完整帧;
|
|
||||||
// 2. 使用 C++17 std::vector/std::optional 管理内存与返回值;
|
|
||||||
// 3. placement new(<new> 头文件)在已有内存上构造上下文对象。
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "tacdec.h"
|
|
||||||
|
|
||||||
#include <algorithm> // std::find 等算法,用于搜索起始符
|
|
||||||
#include <cstddef>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <new> // placement new:在已分配内存上构造对象
|
|
||||||
|
|
||||||
namespace ffmsep::tactile { // 与头文件保持一致的命名空间层次
|
|
||||||
|
|
||||||
namespace { // 匿名命名空间,用于封装文件内部的工具常量与函数
|
|
||||||
|
|
||||||
constexpr std::size_t kMinimumFrameSize = 1 // start 起始符
|
|
||||||
+ 1 // address 设备地址
|
|
||||||
+ 1 // function 功能码
|
|
||||||
+ 1 // length 数据长度
|
|
||||||
+ 0 // payload 最短为 0
|
|
||||||
+ 2 // CRC 校验
|
|
||||||
+ 2; // end markers 结束符
|
|
||||||
|
|
||||||
constexpr std::uint16_t kCrcInitial = 0xFFFF; // CRC 初始值
|
|
||||||
constexpr std::uint16_t kCrcPolynomial = 0xA001; // CRC-16/MODBUS 多项式 (LSB-first)
|
|
||||||
|
|
||||||
// 解码器私有上下文,跟随 CPCodecContext 的 priv_data 生命周期。
|
|
||||||
struct TactileDecoderContext {
|
|
||||||
std::vector<std::uint8_t> fifo; // 环形缓冲,存储尚未解析的原始字节
|
|
||||||
bool end_of_stream = false; // 标记是否收到流结尾
|
|
||||||
std::int64_t next_pts = 0; // 输出帧的自增 pts
|
|
||||||
};
|
|
||||||
|
|
||||||
std::uint16_t crc16_modbus(const std::uint8_t* data, std::size_t length) {
|
|
||||||
std::uint16_t crc = kCrcInitial;
|
|
||||||
for (std::size_t i = 0; i < length; ++i) {
|
|
||||||
crc ^= static_cast<std::uint16_t>(data[i]);
|
|
||||||
for (int bit = 0; bit < 8; ++bit) {
|
|
||||||
if ((crc & 0x0001U) != 0U) {
|
|
||||||
crc = static_cast<std::uint16_t>((crc >> 1U) ^ kCrcPolynomial); // LSB 为 1 时异或多项式
|
|
||||||
} else {
|
|
||||||
crc = static_cast<std::uint16_t>(crc >> 1U); // 否则右移
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return crc;
|
|
||||||
}
|
|
||||||
|
|
||||||
TactileDecoderContext* get_priv(CPCodecContext* ctx) {
|
|
||||||
return ctx ? ctx->priv_as<TactileDecoderContext>() : nullptr; // 使用模板封装的安全转换
|
|
||||||
}
|
|
||||||
|
|
||||||
int tactile_init(CPCodecContext* ctx) {
|
|
||||||
if (!ctx) {
|
|
||||||
return CP_ERROR_INVALID_ARGUMENT;
|
|
||||||
}
|
|
||||||
if (!ctx->priv_data) {
|
|
||||||
ctx->ensure_priv_storage(sizeof(TactileDecoderContext));
|
|
||||||
}
|
|
||||||
auto* storage = static_cast<TactileDecoderContext*>(ctx->priv_data);
|
|
||||||
new (storage) TactileDecoderContext(); // placement new:在已分配的缓冲区中原地构造对象
|
|
||||||
return CP_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
void tactile_close(CPCodecContext* ctx) {
|
|
||||||
if (!ctx || !ctx->priv_data) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (auto* priv = get_priv(ctx); priv != nullptr) {
|
|
||||||
priv->~TactileDecoderContext(); // 显式调用析构函数,释放内部 std::vector
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int tactile_send_packet(CPCodecContext* ctx, const CPPacket& packet) {
|
|
||||||
auto* priv = get_priv(ctx);
|
|
||||||
if (!priv) {
|
|
||||||
return CP_ERROR_INVALID_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet.flush) {
|
|
||||||
priv->fifo.clear(); // flush: 清除所有缓存,回到初始状态
|
|
||||||
priv->end_of_stream = false;
|
|
||||||
priv->next_pts = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!packet.payload.empty()) {
|
|
||||||
priv->fifo.insert(priv->fifo.end(), packet.payload.begin(), packet.payload.end()); // 拼接新字节
|
|
||||||
}
|
|
||||||
|
|
||||||
if (packet.end_of_stream) {
|
|
||||||
priv->end_of_stream = true; // 标记输入源已结束
|
|
||||||
}
|
|
||||||
|
|
||||||
return CP_SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t frame_length_from_payload(std::uint8_t payload_length) {
|
|
||||||
return 1U + 1U + 1U + 1U + payload_length + 2U + 2U; // 计算完整帧总长度
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::uint8_t* buffer_data(const std::vector<std::uint8_t>& buf) {
|
|
||||||
return buf.empty() ? nullptr : buf.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
int tactile_receive_frame(CPCodecContext* ctx, CPFrame& frame) {
|
|
||||||
auto* priv = get_priv(ctx);
|
|
||||||
if (!priv) {
|
|
||||||
return CP_ERROR_INVALID_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& buf = priv->fifo;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (buf.empty()) {
|
|
||||||
if (priv->end_of_stream) {
|
|
||||||
priv->end_of_stream = false;
|
|
||||||
return CP_ERROR_EOF; // 没有数据且流结束
|
|
||||||
}
|
|
||||||
return CP_ERROR_EAGAIN; // 告诉调用者需要更多数据
|
|
||||||
}
|
|
||||||
|
|
||||||
// Discard bytes until start byte is found.
|
|
||||||
auto start_it = std::find(buf.begin(), buf.end(), kStartByte);
|
|
||||||
if (start_it == buf.end()) {
|
|
||||||
buf.clear();
|
|
||||||
if (priv->end_of_stream) {
|
|
||||||
priv->end_of_stream = false;
|
|
||||||
return CP_ERROR_EOF;
|
|
||||||
}
|
|
||||||
return CP_ERROR_EAGAIN;
|
|
||||||
}
|
|
||||||
if (start_it != buf.begin()) {
|
|
||||||
buf.erase(buf.begin(), start_it); // 丢掉起始符前的噪声
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buf.size() < kMinimumFrameSize) {
|
|
||||||
if (priv->end_of_stream) {
|
|
||||||
// Incomplete frame at end of stream: drop it and report EOF.
|
|
||||||
buf.clear();
|
|
||||||
priv->end_of_stream = false;
|
|
||||||
return CP_ERROR_EOF;
|
|
||||||
}
|
|
||||||
return CP_ERROR_EAGAIN; // 帧尚未完整,继续等待
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::uint8_t* data = buffer_data(buf);
|
|
||||||
const std::uint8_t address = data[1];
|
|
||||||
const std::uint8_t function = data[2];
|
|
||||||
const std::uint8_t payload_length = data[3];
|
|
||||||
|
|
||||||
const std::size_t total_frame_length = frame_length_from_payload(payload_length); // 根据长度字段推算完整帧尺寸
|
|
||||||
if (buf.size() < total_frame_length) {
|
|
||||||
if (priv->end_of_stream) {
|
|
||||||
// Not enough data before stream end: treat as EOF and drop buffer.
|
|
||||||
buf.clear();
|
|
||||||
priv->end_of_stream = false;
|
|
||||||
return CP_ERROR_EOF;
|
|
||||||
}
|
|
||||||
return CP_ERROR_EAGAIN;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::size_t payload_offset = 4U;
|
|
||||||
const std::size_t crc_offset = payload_offset + payload_length;
|
|
||||||
const std::size_t end_offset = crc_offset + 2U; // CRC 后紧接 0x0D 0x0A
|
|
||||||
|
|
||||||
const std::uint8_t crc_lo = data[crc_offset];
|
|
||||||
const std::uint8_t crc_hi = data[crc_offset + 1U];
|
|
||||||
const std::uint16_t crc_value = static_cast<std::uint16_t>(crc_lo) |
|
|
||||||
static_cast<std::uint16_t>(crc_hi << 8U);
|
|
||||||
|
|
||||||
const std::uint8_t end_first = data[end_offset];
|
|
||||||
const std::uint8_t end_second = data[end_offset + 1U];
|
|
||||||
|
|
||||||
if (end_first != kEndByteFirst || end_second != kEndByteSecond) {
|
|
||||||
// Invalid end marker, drop start byte and retry.
|
|
||||||
buf.erase(buf.begin());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::size_t crc_region_length = 3U + payload_length; // address + function + length + payload
|
|
||||||
const std::uint16_t computed_crc = crc16_modbus(data + 1U, crc_region_length);
|
|
||||||
if (computed_crc != crc_value) {
|
|
||||||
buf.erase(buf.begin()); // CRC 校验失败,丢弃该起始符并重新同步
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
(void)address; // 当前实现仅检查 CRC,不直接使用地址/功能码
|
|
||||||
(void)function;
|
|
||||||
|
|
||||||
frame.data.assign(buf.begin(), buf.begin() + static_cast<std::ptrdiff_t>(total_frame_length));
|
|
||||||
frame.pts = priv->next_pts++;
|
|
||||||
frame.key_frame = true;
|
|
||||||
frame.valid = true;
|
|
||||||
|
|
||||||
buf.erase(buf.begin(), buf.begin() + static_cast<std::ptrdiff_t>(total_frame_length)); // 移除已消费的内容
|
|
||||||
|
|
||||||
return CP_SUCCESS;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const CPCodec kTactileCodec {
|
|
||||||
"tactile_serial",
|
|
||||||
"Framed tactile sensor serial protocol decoder",
|
|
||||||
CPMediaType::Data,
|
|
||||||
CPCodecID::Tactile,
|
|
||||||
sizeof(TactileDecoderContext),
|
|
||||||
&tactile_init,
|
|
||||||
&tactile_close,
|
|
||||||
&tactile_send_packet,
|
|
||||||
&tactile_receive_frame
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
// 将底层 CPFrame 转换为协议专用的 TactileFrame,失败时返回 std::nullopt。
|
|
||||||
std::optional<TactileFrame> parse_frame(const CPFrame& frame) {
|
|
||||||
if (!frame.valid || frame.data.size() < kMinimumFrameSize) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
const auto* bytes = frame.data.data();
|
|
||||||
const std::size_t size = frame.data.size();
|
|
||||||
|
|
||||||
if (bytes[0] != kStartByte) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
if (bytes[size - 2] != kEndByteFirst || bytes[size - 1] != kEndByteSecond) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (size < 4U) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::uint8_t length = bytes[3];
|
|
||||||
if (frame_length_from_payload(length) != size) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::uint8_t address = bytes[1];
|
|
||||||
const FunctionCode function = static_cast<FunctionCode>(bytes[2]);
|
|
||||||
const std::size_t payload_offset = 4U;
|
|
||||||
|
|
||||||
TactileFrame parsed{};
|
|
||||||
parsed.device_address = address;
|
|
||||||
parsed.function = function;
|
|
||||||
parsed.data_length = length;
|
|
||||||
parsed.payload.assign(bytes + payload_offset, bytes + payload_offset + length);
|
|
||||||
return parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将数据域按照小端排列的 16 位压力值序列解析为整数数组。
|
|
||||||
std::vector<std::uint16_t> parse_pressure_values(const TactileFrame& frame) {
|
|
||||||
if (frame.payload.empty() || (frame.payload.size() % 2U != 0U)) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
std::vector<std::uint16_t> values;
|
|
||||||
values.reserve(frame.payload.size() / 2U);
|
|
||||||
for (std::size_t idx = 0; idx + 1U < frame.payload.size(); idx += 2U) {
|
|
||||||
const std::uint16_t value = static_cast<std::uint16_t>(
|
|
||||||
static_cast<std::uint16_t>(frame.payload[idx]) |
|
|
||||||
static_cast<std::uint16_t>(frame.payload[idx + 1U] << 8U));
|
|
||||||
values.push_back(value);
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析矩阵尺寸载荷:两个字节分别为长边/短边。
|
|
||||||
std::optional<MatrixSize> parse_matrix_size_payload(const TactileFrame& frame) {
|
|
||||||
if (frame.payload.size() != 2U) {
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
MatrixSize size{};
|
|
||||||
size.long_edge = frame.payload[0];
|
|
||||||
size.short_edge = frame.payload[1];
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析矩阵坐标:协议格式与尺寸一致,直接复用逻辑。
|
|
||||||
std::optional<MatrixSize> parse_matrix_coordinate_payload(const TactileFrame& frame) {
|
|
||||||
return parse_matrix_size_payload(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提供对外查询:返回触觉协议对应的 CPCodec 描述。
|
|
||||||
const CPCodec* tactile_codec() {
|
|
||||||
return &kTactileCodec;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将触觉协议编解码器注册到全局列表,供 cpcodec_find_decoder 使用。
|
|
||||||
void register_tactile_codec() {
|
|
||||||
cpcodec_register(&kTactileCodec);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ffmsep::tactile
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
//
|
|
||||||
// Core FFmpeg-style codec registry and decoding helpers.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "cpdecoder.hh"
|
#include "cpdecoder.hh"
|
||||||
|
#include "components/ffmsep/cpdecoder.hh"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <initializer_list>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace ffmsep {
|
namespace ffmsep {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
std::vector<const CPCodec*>& codec_registry() {
|
std::vector<const CPCodec*>& codec_registry() {
|
||||||
@@ -28,7 +28,7 @@ void attach_codec(CPCodecContext* ctx, const CPCodec* codec) {
|
|||||||
|
|
||||||
ctx->codec = codec;
|
ctx->codec = codec;
|
||||||
if (!codec) {
|
if (!codec) {
|
||||||
ctx->codec_type = CPMediaType::Unknown;
|
ctx->codec_type = CPMediaType::Data;
|
||||||
ctx->priv_data = nullptr;
|
ctx->priv_data = nullptr;
|
||||||
ctx->release_priv_storage();
|
ctx->release_priv_storage();
|
||||||
return;
|
return;
|
||||||
@@ -42,11 +42,12 @@ bool codec_name_equals(const CPCodec* codec, std::string_view name) {
|
|||||||
if (!codec || !codec->name) {
|
if (!codec || !codec->name) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::string_view(codec->name) == name;
|
return std::string_view(codec->name) == name;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
using namespace ffmsep;
|
||||||
void* CPCodecContext::ensure_priv_storage(std::size_t size) {
|
void* CPCodecContext::ensure_priv_storage(std::size_t size) {
|
||||||
if (size == 0U) {
|
if (size == 0U) {
|
||||||
priv_storage.clear();
|
priv_storage.clear();
|
||||||
@@ -65,7 +66,7 @@ void CPCodecContext::release_priv_storage() noexcept {
|
|||||||
priv_data = nullptr;
|
priv_data = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cpcodec_register(const CPCodec* codec) {
|
void cpcodec_register(const CPCodec *codec) {
|
||||||
if (!codec || !codec->name) {
|
if (!codec || !codec->name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -78,7 +79,7 @@ void cpcodec_register(const CPCodec* codec) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto same_id = std::find_if(reg.begin(), reg.end(), [codec](const CPCodec* entry) {
|
auto same_id = std::find_if(reg.begin(), reg.end(), [codec](const CPCodec* entry) {
|
||||||
return entry && codec && entry->id == codec->id && codec->id != CPCodecID::Unknown;
|
return entry && codec && entry->id == codec->id && codec->id != CPCodecID::Unknow;
|
||||||
});
|
});
|
||||||
if (same_id != reg.end()) {
|
if (same_id != reg.end()) {
|
||||||
*same_id = codec;
|
*same_id = codec;
|
||||||
@@ -97,7 +98,7 @@ void cpcodec_register_many(std::initializer_list<const CPCodec*> codecs) {
|
|||||||
const CPCodec* cpcodec_find_decoder(CPCodecID id) {
|
const CPCodec* cpcodec_find_decoder(CPCodecID id) {
|
||||||
std::lock_guard<std::mutex> lock(registry_mutex());
|
std::lock_guard<std::mutex> lock(registry_mutex());
|
||||||
const auto& reg = codec_registry();
|
const auto& reg = codec_registry();
|
||||||
auto it = std::find_if(reg.begin(), reg.end(), [id](const CPCodec* codec) {
|
auto it = std::find_if(reg.begin(), reg.end(), [id](const CPCodec* codec){
|
||||||
return codec && codec->id == id;
|
return codec && codec->id == id;
|
||||||
});
|
});
|
||||||
return it == reg.end() ? nullptr : *it;
|
return it == reg.end() ? nullptr : *it;
|
||||||
@@ -106,10 +107,11 @@ const CPCodec* cpcodec_find_decoder(CPCodecID id) {
|
|||||||
const CPCodec* cpcodec_find_decoder_by_name(std::string_view name) {
|
const CPCodec* cpcodec_find_decoder_by_name(std::string_view name) {
|
||||||
std::lock_guard<std::mutex> lock(registry_mutex());
|
std::lock_guard<std::mutex> lock(registry_mutex());
|
||||||
const auto& reg = codec_registry();
|
const auto& reg = codec_registry();
|
||||||
auto it = std::find_if(reg.begin(), reg.end(), [name](const CPCodec* codec) {
|
auto it = std::find_if(reg.begin(), reg.end(), [name](const CPCodec* codec){
|
||||||
return codec_name_equals(codec, name);
|
return codec_name_equals(codec, name);
|
||||||
});
|
});
|
||||||
return it == reg.end() ? nullptr : *it;
|
|
||||||
|
return it ==reg.end() ? nullptr : *it;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<const CPCodec*> cpcodec_list_codecs() {
|
std::vector<const CPCodec*> cpcodec_list_codecs() {
|
||||||
@@ -117,15 +119,16 @@ std::vector<const CPCodec*> cpcodec_list_codecs() {
|
|||||||
return codec_registry();
|
return codec_registry();
|
||||||
}
|
}
|
||||||
|
|
||||||
CPCodecContext* cpcodec_alloc_context3(const CPCodec* codec) {
|
CPCodecContext* cpcodec_alloc_context(const CPCodec* codec) {
|
||||||
auto* ctx = new CPCodecContext();
|
auto* ctx = new CPCodecContext();
|
||||||
if (codec) {
|
if (codec) {
|
||||||
attach_codec(ctx, codec);
|
attach_codec(ctx, codec);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ctx;
|
return ctx;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cpcodec_open2(CPCodecContext* ctx, const CPCodec* codec) {
|
int cpcodec_open(CPCodecContext* ctx, const CPCodec* codec) {
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
return CP_ERROR_INVALID_ARGUMENT;
|
return CP_ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
@@ -153,10 +156,11 @@ int cpcodec_open2(CPCodecContext* ctx, const CPCodec* codec) {
|
|||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CP_SUCCESS;
|
return CP_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cpcodec_close(CPCodecContext* ctx) {
|
int cpcodec_close(CPCodecContext *ctx) {
|
||||||
if (!ctx) {
|
if (!ctx) {
|
||||||
return CP_ERROR_INVALID_ARGUMENT;
|
return CP_ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
@@ -169,28 +173,33 @@ int cpcodec_close(CPCodecContext* ctx) {
|
|||||||
ctx->codec->close(ctx);
|
ctx->codec->close(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ctx->codec && ctx->codec->close) {
|
||||||
|
ctx->codec->close(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
ctx->is_open = false;
|
ctx->is_open = false;
|
||||||
ctx->release_priv_storage();
|
ctx->release_priv_storage();
|
||||||
ctx->codec_type = CPMediaType::Unknown;
|
ctx->codec_type = CPMediaType::Unknow;
|
||||||
ctx->codec = nullptr;
|
ctx->codec = nullptr;
|
||||||
ctx->priv_data = nullptr;
|
ctx->priv_data = nullptr;
|
||||||
return CP_SUCCESS;
|
return CP_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cpcodec_free_context(CPCodecContext** ctx) {
|
void cpcodec_free_context(CPCodecContext **ctx) {
|
||||||
if (!ctx || !*ctx) {
|
if (!ctx || !*ctx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cpcodec_close(*ctx);
|
cpcodec_close(*ctx);
|
||||||
delete *ctx;
|
delete *ctx;
|
||||||
*ctx = nullptr;
|
*ctx = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
int cpcodec_send_packet(CPCodecContext* ctx, const CPPacket* packet) {
|
int cpcodec_send_packet(CPCodecContext *ctx, const CPPacket *packet) {
|
||||||
if (!ctx || !packet) {
|
if (!ctx || !packet) {
|
||||||
return CP_ERROR_INVALID_ARGUMENT;
|
return CP_ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
if (!ctx->is_open || !ctx->codec) {
|
if (!ctx || !ctx->codec) {
|
||||||
return CP_ERROR_NOT_OPEN;
|
return CP_ERROR_NOT_OPEN;
|
||||||
}
|
}
|
||||||
if (!ctx->codec->send_packet) {
|
if (!ctx->codec->send_packet) {
|
||||||
@@ -199,7 +208,7 @@ int cpcodec_send_packet(CPCodecContext* ctx, const CPPacket* packet) {
|
|||||||
return ctx->codec->send_packet(ctx, *packet);
|
return ctx->codec->send_packet(ctx, *packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
int cpcodec_receive_frame(CPCodecContext* ctx, CPFrame* frame) {
|
int cpcodec_receive_frame(CPCodecContext *ctx, CPFrame *frame) {
|
||||||
if (!ctx || !frame) {
|
if (!ctx || !frame) {
|
||||||
return CP_ERROR_INVALID_ARGUMENT;
|
return CP_ERROR_INVALID_ARGUMENT;
|
||||||
}
|
}
|
||||||
@@ -211,5 +220,4 @@ int cpcodec_receive_frame(CPCodecContext* ctx, CPFrame* frame) {
|
|||||||
}
|
}
|
||||||
return ctx->codec->receive_frame(ctx, *frame);
|
return ctx->codec->receive_frame(ctx, *frame);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} // namespace ffmsep
|
|
||||||
@@ -1,9 +1,6 @@
|
|||||||
//
|
|
||||||
// Simple FFmpeg-inspired serial decoding toolkit.
|
|
||||||
//
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "components/ffmsep/cpdecoder.hh"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@@ -15,7 +12,6 @@
|
|||||||
|
|
||||||
namespace ffmsep {
|
namespace ffmsep {
|
||||||
|
|
||||||
// Error codes loosely mirroring FFmpeg semantics.
|
|
||||||
inline constexpr int CP_SUCCESS = 0;
|
inline constexpr int CP_SUCCESS = 0;
|
||||||
inline constexpr int CP_ERROR_EOF = -1;
|
inline constexpr int CP_ERROR_EOF = -1;
|
||||||
inline constexpr int CP_ERROR_EAGAIN = -2;
|
inline constexpr int CP_ERROR_EAGAIN = -2;
|
||||||
@@ -24,15 +20,13 @@ inline constexpr int CP_ERROR_INVALID_STATE = -4;
|
|||||||
inline constexpr int CP_ERROR_INVALID_ARGUMENT = -5;
|
inline constexpr int CP_ERROR_INVALID_ARGUMENT = -5;
|
||||||
|
|
||||||
enum class CPMediaType : std::uint8_t {
|
enum class CPMediaType : std::uint8_t {
|
||||||
Unknown = 0,
|
Unknow = 0,
|
||||||
Data,
|
Data,
|
||||||
Audio,
|
|
||||||
Video
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class CPCodecID : std::uint32_t {
|
enum class CPCodecID : std::uint32_t {
|
||||||
Unknown = 0,
|
Unknow = 0,
|
||||||
Tactile = 0x54514354u // 'T','Q','C','T' marker for tactile quick codec.
|
Tactile = 0x54514354u // 'T','Q','C','T':触觉传感器协议标识 Tactile Quick Codec Type
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CPPacket {
|
struct CPPacket {
|
||||||
@@ -46,7 +40,7 @@ struct CPPacket {
|
|||||||
CPPacket(std::vector<std::uint8_t> data, std::int64_t pts_value = 0, std::int64_t dts_value = 0) noexcept
|
CPPacket(std::vector<std::uint8_t> data, std::int64_t pts_value = 0, std::int64_t dts_value = 0) noexcept
|
||||||
: payload(std::move(data)), pts(pts_value), dts(dts_value) {}
|
: payload(std::move(data)), pts(pts_value), dts(dts_value) {}
|
||||||
|
|
||||||
[[nodiscard]] bool empty() const noexcept { return payload.empty(); }
|
[[nodiscard]] bool empty() const noexcept {return payload.empty();}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CPFrame {
|
struct CPFrame {
|
||||||
@@ -57,24 +51,24 @@ struct CPFrame {
|
|||||||
|
|
||||||
void reset() noexcept {
|
void reset() noexcept {
|
||||||
data.clear();
|
data.clear();
|
||||||
pts = 0;
|
|
||||||
key_frame = false;
|
key_frame = false;
|
||||||
valid = false;
|
valid = false;
|
||||||
|
pts = 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CPCodecContext;
|
struct CPCodecContext;
|
||||||
|
|
||||||
struct CPCodec {
|
struct CPCodec {
|
||||||
using InitFn = int (*)(CPCodecContext*);
|
using InitFn = int(*)(CPCodecContext*);
|
||||||
using CloseFn = void (*)(CPCodecContext*);
|
using CloseFn = void(*)(CPCodecContext*);
|
||||||
using SendPacketFn = int (*)(CPCodecContext*, const CPPacket&);
|
using SendPacketFn = int(*)(CPCodecContext*, const CPPacket&);
|
||||||
using ReceiveFrameFn = int (*)(CPCodecContext*, CPFrame&);
|
using ReceiveFrameFn = int(*)(CPCodecContext*, CPFrame&);
|
||||||
|
|
||||||
const char* name = nullptr;
|
const char* name = nullptr;
|
||||||
const char* long_name = nullptr;
|
const char* long_name = nullptr;
|
||||||
CPMediaType type = CPMediaType::Unknown;
|
CPMediaType type = CPMediaType::Unknow;
|
||||||
CPCodecID id = CPCodecID::Unknown;
|
CPCodecID id = CPCodecID::Unknow;
|
||||||
std::size_t priv_data_size = 0;
|
std::size_t priv_data_size = 0;
|
||||||
InitFn init = nullptr;
|
InitFn init = nullptr;
|
||||||
CloseFn close = nullptr;
|
CloseFn close = nullptr;
|
||||||
@@ -85,13 +79,13 @@ struct CPCodec {
|
|||||||
struct CPCodecContext {
|
struct CPCodecContext {
|
||||||
const CPCodec* codec = nullptr;
|
const CPCodec* codec = nullptr;
|
||||||
void* priv_data = nullptr;
|
void* priv_data = nullptr;
|
||||||
CPMediaType codec_type = CPMediaType::Unknown;
|
CPMediaType codec_type = CPMediaType::Unknow;
|
||||||
bool is_open = false;
|
bool is_open = false;
|
||||||
|
|
||||||
void clear() noexcept {
|
void clear() noexcept {
|
||||||
codec = nullptr;
|
codec = nullptr;
|
||||||
priv_data = nullptr;
|
priv_data = nullptr;
|
||||||
codec_type = CPMediaType::Unknown;
|
codec_type = CPMediaType::Unknow;
|
||||||
is_open = false;
|
is_open = false;
|
||||||
priv_storage.clear();
|
priv_storage.clear();
|
||||||
}
|
}
|
||||||
@@ -99,21 +93,20 @@ struct CPCodecContext {
|
|||||||
void* ensure_priv_storage(std::size_t size);
|
void* ensure_priv_storage(std::size_t size);
|
||||||
void release_priv_storage() noexcept;
|
void release_priv_storage() noexcept;
|
||||||
|
|
||||||
template <typename T>
|
template<typename T>
|
||||||
[[nodiscard]] T* priv_as() noexcept {
|
[[nodiscard]] T* priv_as() noexcept {
|
||||||
return static_cast<T*>(priv_data);
|
return static_cast<T*>(priv_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template<typename T>
|
||||||
[[nodiscard]] const T* priv_as() const noexcept {
|
[[nodiscard]] const T* priv_as() const noexcept {
|
||||||
return static_cast<const T*>(priv_data);
|
return static_cast<const T*>(priv_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<std::uint8_t> priv_storage;
|
std::vector<std::uint8_t> priv_storage;
|
||||||
|
friend CPCodecContext* cpcodec_alloc_context(const CPCodec*);
|
||||||
friend CPCodecContext* cpcodec_alloc_context3(const CPCodec*);
|
friend int cpcodec_open(CPCodecContext*, const CPCodec*);
|
||||||
friend int cpcodec_open2(CPCodecContext*, const CPCodec*);
|
|
||||||
friend int cpcodec_close(CPCodecContext*);
|
friend int cpcodec_close(CPCodecContext*);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -123,11 +116,10 @@ const CPCodec* cpcodec_find_decoder(CPCodecID id);
|
|||||||
const CPCodec* cpcodec_find_decoder_by_name(std::string_view name);
|
const CPCodec* cpcodec_find_decoder_by_name(std::string_view name);
|
||||||
std::vector<const CPCodec*> cpcodec_list_codecs();
|
std::vector<const CPCodec*> cpcodec_list_codecs();
|
||||||
|
|
||||||
CPCodecContext* cpcodec_alloc_context3(const CPCodec* codec);
|
CPCodecContext* cpcodec_alloc_context(const CPCodec* codec);
|
||||||
int cpcodec_open2(CPCodecContext* ctx, const CPCodec* codec = nullptr);
|
int cpcodec_open(CPCodecContext*, const CPCodec*);
|
||||||
int cpcodec_close(CPCodecContext* ctx);
|
int cpcodec_close(CPCodecContext*);
|
||||||
void cpcodec_free_context(CPCodecContext** ctx);
|
void cpcodec_free_context(CPCodecContext **ctx);
|
||||||
int cpcodec_send_packet(CPCodecContext* ctx, const CPPacket* packet);
|
int cpcodec_send_packet(CPCodecContext*, const CPPacket*);
|
||||||
int cpcodec_receive_frame(CPCodecContext* ctx, CPFrame* frame);
|
int cpcodec_receive_frame(CPCodecContext*, CPFrame*);
|
||||||
|
}
|
||||||
} // namespace ffmsep
|
|
||||||
627
components/ffmsep/cpstream_core.cc
Normal file
627
components/ffmsep/cpstream_core.cc
Normal file
@@ -0,0 +1,627 @@
|
|||||||
|
#include "components/ffmsep/cpstream_core.hh"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <deque>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace ffmsep {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kReaderIdleSleep = std::chrono::milliseconds(5);
|
||||||
|
constexpr auto kDecoderIdleSleep = std::chrono::milliseconds(1);
|
||||||
|
|
||||||
|
const CPCodec* resolve_requested_codec(const CPStreamConfig& config) {
|
||||||
|
if (!config.codec_name.empty()) {
|
||||||
|
if (const CPCodec* codec = cpcodec_find_decoder_by_name(config.codec_name)) {
|
||||||
|
return codec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (config.codec_id != CPCodecID::Unknow) {
|
||||||
|
if (const CPCodec* codec = cpcodec_find_decoder(config.codec_id)) {
|
||||||
|
return codec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
struct CPStreamCore::Impl {
|
||||||
|
struct Packet {
|
||||||
|
std::vector<std::uint8_t> payload;
|
||||||
|
std::int64_t pts = 0;
|
||||||
|
bool end_of_stream = false;
|
||||||
|
bool flush = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit Impl(CPStreamConfig config)
|
||||||
|
: config_(std::move(config)) {
|
||||||
|
normalize_config();
|
||||||
|
}
|
||||||
|
|
||||||
|
~Impl() = default;
|
||||||
|
|
||||||
|
void normalize_config() {
|
||||||
|
if (config_.read_chunk_size == 0U) {
|
||||||
|
config_.read_chunk_size = 256U;
|
||||||
|
}
|
||||||
|
if (config_.packet_queue_capacity == 0U) {
|
||||||
|
config_.packet_queue_capacity = 1U;
|
||||||
|
}
|
||||||
|
if (config_.frame_queue_capacity == 0U) {
|
||||||
|
config_.frame_queue_capacity = 1U;
|
||||||
|
}
|
||||||
|
frame_queue_capacity_ = config_.frame_queue_capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool open(const CPStreamConfig& cfg) {
|
||||||
|
stop();
|
||||||
|
close();
|
||||||
|
|
||||||
|
config_ = cfg;
|
||||||
|
normalize_config();
|
||||||
|
|
||||||
|
if (config_.port.empty()) {
|
||||||
|
set_last_error("serial port is empty");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
codec_descriptor_ = resolve_requested_codec(config_);
|
||||||
|
if (!codec_descriptor_) {
|
||||||
|
set_last_error("codec not found for requested identifier");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
codec_ctx_ = cpcodec_alloc_context(codec_descriptor_);
|
||||||
|
if (!codec_ctx_) {
|
||||||
|
set_last_error("failed to allocate codec context");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = cpcodec_open(codec_ctx_, codec_descriptor_);
|
||||||
|
if (rc < CP_SUCCESS) {
|
||||||
|
set_last_error("failed to open codec context: error " + std::to_string(rc));
|
||||||
|
cpcodec_free_context(&codec_ctx_);
|
||||||
|
codec_ctx_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto serial = std::make_shared<serial::Serial>(
|
||||||
|
config_.port,
|
||||||
|
config_.baudrate,
|
||||||
|
config_.timeout,
|
||||||
|
config_.bytesize,
|
||||||
|
config_.parity,
|
||||||
|
config_.stopbits,
|
||||||
|
config_.flowcontrol);
|
||||||
|
serial->open();
|
||||||
|
serial->flush();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lock(serial_mutex_);
|
||||||
|
serial_ = std::move(serial);
|
||||||
|
}
|
||||||
|
} catch (const serial::IOException& ex) {
|
||||||
|
set_last_error(ex.what() ? ex.what() : "serial IO exception");
|
||||||
|
cpcodec_close(codec_ctx_);
|
||||||
|
cpcodec_free_context(&codec_ctx_);
|
||||||
|
codec_ctx_ = nullptr;
|
||||||
|
return false;
|
||||||
|
} catch (const serial::SerialException& ex) {
|
||||||
|
set_last_error(ex.what() ? ex.what() : "serial exception");
|
||||||
|
cpcodec_close(codec_ctx_);
|
||||||
|
cpcodec_free_context(&codec_ctx_);
|
||||||
|
codec_ctx_ = nullptr;
|
||||||
|
return false;
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
set_last_error(ex.what());
|
||||||
|
cpcodec_close(codec_ctx_);
|
||||||
|
cpcodec_free_context(&codec_ctx_);
|
||||||
|
codec_ctx_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lock(packet_mutex_);
|
||||||
|
packet_queue_.clear();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard lock(frame_mutex_);
|
||||||
|
frame_queue_.clear();
|
||||||
|
}
|
||||||
|
pts_counter_.store(0, std::memory_order_relaxed);
|
||||||
|
stop_requested_.store(false, std::memory_order_release);
|
||||||
|
set_last_error({});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool open() {
|
||||||
|
return open(config_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool reopen(const CPStreamConfig& cfg) {
|
||||||
|
return open(cfg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void close() {
|
||||||
|
stop();
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lock(serial_mutex_);
|
||||||
|
if (serial_) {
|
||||||
|
try {
|
||||||
|
if (serial_->isOpen()) {
|
||||||
|
serial_->close();
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
// Ignore close errors.
|
||||||
|
}
|
||||||
|
serial_.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codec_ctx_) {
|
||||||
|
cpcodec_close(codec_ctx_);
|
||||||
|
cpcodec_free_context(&codec_ctx_);
|
||||||
|
codec_ctx_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lock(packet_mutex_);
|
||||||
|
packet_queue_.clear();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard lock(frame_mutex_);
|
||||||
|
frame_queue_.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool start() {
|
||||||
|
if (running_.load(std::memory_order_acquire)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<serial::Serial> serial_copy;
|
||||||
|
{
|
||||||
|
std::lock_guard lock(serial_mutex_);
|
||||||
|
serial_copy = serial_;
|
||||||
|
}
|
||||||
|
if (!serial_copy || !serial_copy->isOpen()) {
|
||||||
|
set_last_error("serial port is not open");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!codec_ctx_ || !codec_ctx_->is_open) {
|
||||||
|
set_last_error("codec context is not ready");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_requested_.store(false, std::memory_order_release);
|
||||||
|
running_.store(true, std::memory_order_release);
|
||||||
|
|
||||||
|
reader_thread_ = std::thread(&Impl::reader_loop, this);
|
||||||
|
decoder_thread_ = std::thread(&Impl::decoder_loop, this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
if (!running_.exchange(false, std::memory_order_acq_rel)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_requested_.store(true, std::memory_order_release);
|
||||||
|
packet_cv_.notify_all();
|
||||||
|
|
||||||
|
if (reader_thread_.joinable()) {
|
||||||
|
reader_thread_.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
signal_decoder_flush(true);
|
||||||
|
packet_cv_.notify_all();
|
||||||
|
|
||||||
|
if (decoder_thread_.joinable()) {
|
||||||
|
decoder_thread_.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_requested_.store(false, std::memory_order_release);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lock(packet_mutex_);
|
||||||
|
packet_queue_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codec_ctx_ && codec_ctx_->is_open) {
|
||||||
|
reset_decoder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_open() const {
|
||||||
|
std::lock_guard lock(serial_mutex_);
|
||||||
|
return serial_ && serial_->isOpen();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_running() const {
|
||||||
|
return running_.load(std::memory_order_acquire);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool send(const std::vector<std::uint8_t>& data) {
|
||||||
|
return send(data.data(), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool send(const std::uint8_t* data, std::size_t size) {
|
||||||
|
if (!data || size == 0U) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<serial::Serial> serial_copy;
|
||||||
|
{
|
||||||
|
std::lock_guard lock(serial_mutex_);
|
||||||
|
serial_copy = serial_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!serial_copy || !serial_copy->isOpen()) {
|
||||||
|
set_last_error("serial port is not open");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const auto written = serial_copy->write(data, size);
|
||||||
|
return written == size;
|
||||||
|
} catch (const serial::IOException& ex) {
|
||||||
|
set_last_error(ex.what() ? ex.what() : "serial IO exception");
|
||||||
|
} catch (const serial::SerialException& ex) {
|
||||||
|
set_last_error(ex.what() ? ex.what() : "serial exception");
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
set_last_error(ex.what());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DecodedFrame> try_pop_frame() {
|
||||||
|
std::lock_guard lock(frame_mutex_);
|
||||||
|
if (frame_queue_.empty()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
DecodedFrame frame = std::move(frame_queue_.front());
|
||||||
|
frame_queue_.pop_front();
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wait_for_frame(DecodedFrame& frame, std::chrono::milliseconds timeout) {
|
||||||
|
std::unique_lock lock(frame_mutex_);
|
||||||
|
if (!frame_cv_.wait_for(lock, timeout, [&] {
|
||||||
|
return !frame_queue_.empty();
|
||||||
|
})) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
frame = std::move(frame_queue_.front());
|
||||||
|
frame_queue_.pop_front();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear_frames() {
|
||||||
|
std::lock_guard lock(frame_mutex_);
|
||||||
|
frame_queue_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_frame_queue_capacity(std::size_t capacity) {
|
||||||
|
if (capacity == 0U) {
|
||||||
|
capacity = 1U;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard lock(frame_mutex_);
|
||||||
|
frame_queue_capacity_ = capacity;
|
||||||
|
config_.frame_queue_capacity = capacity;
|
||||||
|
while (frame_queue_.size() > frame_queue_capacity_) {
|
||||||
|
frame_queue_.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_frame_callback(FrameCallback callback) {
|
||||||
|
std::lock_guard lock(callback_mutex_);
|
||||||
|
frame_callback_ = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
CPStreamConfig config() const {
|
||||||
|
return config_;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string last_error() const {
|
||||||
|
std::lock_guard lock(last_error_mutex_);
|
||||||
|
return last_error_;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::vector<serial::PortInfo> list_ports() {
|
||||||
|
return serial::list_ports();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reader_loop() {
|
||||||
|
std::vector<std::uint8_t> buffer(config_.read_chunk_size);
|
||||||
|
|
||||||
|
while (!stop_requested_.load(std::memory_order_acquire)) {
|
||||||
|
std::shared_ptr<serial::Serial> serial_copy;
|
||||||
|
{
|
||||||
|
std::lock_guard lock(serial_mutex_);
|
||||||
|
serial_copy = serial_;
|
||||||
|
}
|
||||||
|
if (!serial_copy || !serial_copy->isOpen()) {
|
||||||
|
std::this_thread::sleep_for(kReaderIdleSleep);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t bytes_read = 0;
|
||||||
|
try {
|
||||||
|
bytes_read = serial_copy->read(buffer.data(), buffer.size());
|
||||||
|
} catch (const serial::IOException& ex) {
|
||||||
|
set_last_error(ex.what() ? ex.what() : "serial IO exception");
|
||||||
|
std::this_thread::sleep_for(kReaderIdleSleep);
|
||||||
|
continue;
|
||||||
|
} catch (const serial::SerialException& ex) {
|
||||||
|
set_last_error(ex.what() ? ex.what() : "serial exception");
|
||||||
|
std::this_thread::sleep_for(kReaderIdleSleep);
|
||||||
|
continue;
|
||||||
|
} catch (const std::exception& ex) {
|
||||||
|
set_last_error(ex.what());
|
||||||
|
std::this_thread::sleep_for(kReaderIdleSleep);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes_read == 0U) {
|
||||||
|
std::this_thread::sleep_for(kReaderIdleSleep);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Packet packet;
|
||||||
|
packet.payload.assign(buffer.begin(), buffer.begin() + static_cast<std::ptrdiff_t>(bytes_read));
|
||||||
|
packet.pts = pts_counter_.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lock(packet_mutex_);
|
||||||
|
if (packet_queue_.size() >= config_.packet_queue_capacity) {
|
||||||
|
packet_queue_.pop_front();
|
||||||
|
}
|
||||||
|
packet_queue_.push_back(std::move(packet));
|
||||||
|
}
|
||||||
|
packet_cv_.notify_one();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void decoder_loop() {
|
||||||
|
while (true) {
|
||||||
|
Packet packet;
|
||||||
|
{
|
||||||
|
std::unique_lock lock(packet_mutex_);
|
||||||
|
packet_cv_.wait(lock, [&] {
|
||||||
|
return stop_requested_.load(std::memory_order_acquire) || !packet_queue_.empty();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (packet_queue_.empty()) {
|
||||||
|
if (stop_requested_.load(std::memory_order_acquire)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
packet = std::move(packet_queue_.front());
|
||||||
|
packet_queue_.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!codec_ctx_ || !codec_ctx_->is_open) {
|
||||||
|
if (packet.end_of_stream) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
std::this_thread::sleep_for(kDecoderIdleSleep);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
CPPacket cp_packet;
|
||||||
|
cp_packet.payload = std::move(packet.payload);
|
||||||
|
cp_packet.pts = packet.pts;
|
||||||
|
cp_packet.dts = packet.pts;
|
||||||
|
cp_packet.end_of_stream = packet.end_of_stream;
|
||||||
|
cp_packet.flush = packet.flush;
|
||||||
|
|
||||||
|
int rc = cpcodec_send_packet(codec_ctx_, &cp_packet);
|
||||||
|
if (rc < CP_SUCCESS) {
|
||||||
|
if (packet.end_of_stream) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
CPFrame frame;
|
||||||
|
rc = cpcodec_receive_frame(codec_ctx_, &frame);
|
||||||
|
if (rc == CP_SUCCESS) {
|
||||||
|
DecodedFrame decoded;
|
||||||
|
decoded.pts = frame.pts;
|
||||||
|
decoded.received_at = std::chrono::steady_clock::now();
|
||||||
|
decoded.frame = std::move(frame);
|
||||||
|
|
||||||
|
FrameCallback callback_copy;
|
||||||
|
{
|
||||||
|
std::lock_guard lock(callback_mutex_);
|
||||||
|
callback_copy = frame_callback_;
|
||||||
|
}
|
||||||
|
if (callback_copy) {
|
||||||
|
callback_copy(decoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard lock(frame_mutex_);
|
||||||
|
if (frame_queue_.size() >= frame_queue_capacity_) {
|
||||||
|
frame_queue_.pop_front();
|
||||||
|
}
|
||||||
|
frame_queue_.push_back(std::move(decoded));
|
||||||
|
}
|
||||||
|
frame_cv_.notify_one();
|
||||||
|
} else if (rc == CP_ERROR_EAGAIN) {
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
if (rc == CP_ERROR_EOF && packet.end_of_stream) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.end_of_stream) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void signal_decoder_flush(bool end_of_stream) {
|
||||||
|
Packet packet;
|
||||||
|
packet.flush = true;
|
||||||
|
packet.end_of_stream = end_of_stream;
|
||||||
|
{
|
||||||
|
std::lock_guard lock(packet_mutex_);
|
||||||
|
packet_queue_.push_back(std::move(packet));
|
||||||
|
}
|
||||||
|
packet_cv_.notify_one();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset_decoder() {
|
||||||
|
if (!codec_ctx_ || !codec_descriptor_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cpcodec_close(codec_ctx_);
|
||||||
|
int rc = cpcodec_open(codec_ctx_, codec_descriptor_);
|
||||||
|
if (rc < CP_SUCCESS) {
|
||||||
|
set_last_error("failed to reset codec context: error " + std::to_string(rc));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_last_error(std::string message) {
|
||||||
|
std::lock_guard lock(last_error_mutex_);
|
||||||
|
last_error_ = std::move(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
CPStreamConfig config_{};
|
||||||
|
const CPCodec* codec_descriptor_ = nullptr;
|
||||||
|
|
||||||
|
std::shared_ptr<serial::Serial> serial_;
|
||||||
|
mutable std::mutex serial_mutex_;
|
||||||
|
|
||||||
|
CPCodecContext* codec_ctx_ = nullptr;
|
||||||
|
|
||||||
|
std::thread reader_thread_;
|
||||||
|
std::thread decoder_thread_;
|
||||||
|
|
||||||
|
std::mutex packet_mutex_;
|
||||||
|
std::condition_variable packet_cv_;
|
||||||
|
std::deque<Packet> packet_queue_;
|
||||||
|
|
||||||
|
std::mutex frame_mutex_;
|
||||||
|
std::condition_variable frame_cv_;
|
||||||
|
std::deque<DecodedFrame> frame_queue_;
|
||||||
|
std::size_t frame_queue_capacity_ = 16;
|
||||||
|
|
||||||
|
FrameCallback frame_callback_;
|
||||||
|
mutable std::mutex callback_mutex_;
|
||||||
|
|
||||||
|
std::atomic<bool> running_{false};
|
||||||
|
std::atomic<bool> stop_requested_{false};
|
||||||
|
std::atomic<std::int64_t> pts_counter_{0};
|
||||||
|
|
||||||
|
std::string last_error_;
|
||||||
|
mutable std::mutex last_error_mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
CPStreamCore::CPStreamCore(CPStreamConfig config)
|
||||||
|
: impl_(std::make_unique<Impl>(std::move(config))) {}
|
||||||
|
|
||||||
|
CPStreamCore::~CPStreamCore() {
|
||||||
|
if (impl_) {
|
||||||
|
impl_->stop();
|
||||||
|
impl_->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPStreamCore::open(const CPStreamConfig& config) {
|
||||||
|
return impl_->open(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPStreamCore::open() {
|
||||||
|
return impl_->open();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPStreamCore::reopen(const CPStreamConfig& config) {
|
||||||
|
return impl_->reopen(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPStreamCore::close() {
|
||||||
|
impl_->close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPStreamCore::start() {
|
||||||
|
return impl_->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPStreamCore::stop() {
|
||||||
|
impl_->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPStreamCore::is_open() const noexcept {
|
||||||
|
return impl_->is_open();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPStreamCore::is_running() const noexcept {
|
||||||
|
return impl_->is_running();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPStreamCore::send(const std::vector<std::uint8_t>& data) {
|
||||||
|
return impl_->send(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPStreamCore::send(const std::uint8_t* data, std::size_t size) {
|
||||||
|
return impl_->send(data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DecodedFrame> CPStreamCore::try_pop_frame() {
|
||||||
|
return impl_->try_pop_frame();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CPStreamCore::wait_for_frame(DecodedFrame& frame, std::chrono::milliseconds timeout) {
|
||||||
|
return impl_->wait_for_frame(frame, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPStreamCore::clear_frames() {
|
||||||
|
impl_->clear_frames();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPStreamCore::set_frame_queue_capacity(std::size_t capacity) {
|
||||||
|
impl_->set_frame_queue_capacity(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CPStreamCore::set_frame_callback(FrameCallback callback) {
|
||||||
|
impl_->set_frame_callback(std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
CPStreamConfig CPStreamCore::config() const {
|
||||||
|
return impl_->config();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CPStreamCore::last_error() const {
|
||||||
|
return impl_->last_error();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<serial::PortInfo> CPStreamCore::list_available_ports() {
|
||||||
|
return Impl::list_ports();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ffmsep
|
||||||
77
components/ffmsep/cpstream_core.hh
Normal file
77
components/ffmsep/cpstream_core.hh
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "components/ffmsep/cpdecoder.hh"
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <serial/serial.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace ffmsep {
|
||||||
|
|
||||||
|
struct DecodedFrame {
|
||||||
|
CPFrame frame;
|
||||||
|
std::chrono::steady_clock::time_point received_at{};
|
||||||
|
std::int64_t pts = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CPStreamConfig {
|
||||||
|
std::string port;
|
||||||
|
std::uint32_t baudrate = 115200;
|
||||||
|
serial::Timeout timeout = serial::Timeout::simpleTimeout(50);
|
||||||
|
serial::bytesize_t bytesize = serial::eightbits;
|
||||||
|
serial::parity_t parity = serial::parity_none;
|
||||||
|
serial::stopbits_t stopbits = serial::stopbits_one;
|
||||||
|
serial::flowcontrol_t flowcontrol = serial::flowcontrol_none;
|
||||||
|
std::size_t read_chunk_size = 256;
|
||||||
|
std::size_t packet_queue_capacity = 128;
|
||||||
|
std::size_t frame_queue_capacity = 16;
|
||||||
|
CPCodecID codec_id = CPCodecID::Unknow;
|
||||||
|
std::string codec_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CPStreamCore {
|
||||||
|
public:
|
||||||
|
using FrameCallback = std::function<void(const DecodedFrame&)>;
|
||||||
|
|
||||||
|
explicit CPStreamCore(CPStreamConfig config = {});
|
||||||
|
~CPStreamCore();
|
||||||
|
|
||||||
|
CPStreamCore(const CPStreamCore&) = delete;
|
||||||
|
CPStreamCore& operator=(const CPStreamCore&) = delete;
|
||||||
|
|
||||||
|
bool open(const CPStreamConfig& config);
|
||||||
|
bool open();
|
||||||
|
bool reopen(const CPStreamConfig& config);
|
||||||
|
void close();
|
||||||
|
|
||||||
|
bool start();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
[[nodiscard]] bool is_open() const noexcept;
|
||||||
|
[[nodiscard]] bool is_running() const noexcept;
|
||||||
|
|
||||||
|
bool send(const std::vector<std::uint8_t>& data);
|
||||||
|
bool send(const std::uint8_t* data, std::size_t size);
|
||||||
|
|
||||||
|
std::optional<DecodedFrame> try_pop_frame();
|
||||||
|
bool wait_for_frame(DecodedFrame& frame, std::chrono::milliseconds timeout);
|
||||||
|
void clear_frames();
|
||||||
|
void set_frame_queue_capacity(std::size_t capacity);
|
||||||
|
|
||||||
|
void set_frame_callback(FrameCallback callback);
|
||||||
|
|
||||||
|
[[nodiscard]] CPStreamConfig config() const;
|
||||||
|
[[nodiscard]] std::string last_error() const;
|
||||||
|
|
||||||
|
static std::vector<serial::PortInfo> list_available_ports();
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Impl;
|
||||||
|
std::unique_ptr<Impl> impl_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ffmsep
|
||||||
@@ -1,28 +1,25 @@
|
|||||||
//
|
#include "tacdec.hh"
|
||||||
// Decoder for the tactile sensor framed binary protocol.
|
#include "components/ffmsep/cpdecoder.hh"
|
||||||
//
|
|
||||||
|
|
||||||
#include "tacdec.h"
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <new>
|
#include <new>
|
||||||
|
#include <optional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace ffmsep::tactile {
|
namespace ffmsep::tactile {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr std::size_t kMinimumFrameSize = 1 // start
|
constexpr std::size_t kMinimumFrameSize = 1
|
||||||
+ 1 // address
|
+ 1
|
||||||
+ 1 // function
|
+ 1
|
||||||
+ 1 // length
|
+ 1
|
||||||
+ 0 // payload
|
+ 0
|
||||||
+ 2 // CRC
|
+ 2
|
||||||
+ 2; // end markers
|
+ 2;
|
||||||
|
|
||||||
constexpr std::uint16_t kCrcInitial = 0xFFFF;
|
constexpr std::uint16_t kCrcInitial = 0xFFFF;
|
||||||
constexpr std::uint16_t kCrcPolynomial = 0xA001; // CRC-16/MODBUS (LSB first)
|
constexpr std::uint16_t kCrcPolynomial = 0xA001;
|
||||||
|
|
||||||
struct TactileDecoderContext {
|
struct TactileDecoderContext {
|
||||||
std::vector<std::uint8_t> fifo;
|
std::vector<std::uint8_t> fifo;
|
||||||
@@ -30,6 +27,14 @@ struct TactileDecoderContext {
|
|||||||
std::int64_t next_pts = 0;
|
std::int64_t next_pts = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::size_t frame_length_from_payload(std::uint8_t payload_length) {
|
||||||
|
return 1U + 1U + 1U + 1U + payload_length + 2U + 2U;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::uint8_t* buffer_data(const std::vector<std::uint8_t>& buf) {
|
||||||
|
return buf.empty() ? nullptr : buf.data();
|
||||||
|
}
|
||||||
|
|
||||||
std::uint16_t crc16_modbus(const std::uint8_t* data, std::size_t length) {
|
std::uint16_t crc16_modbus(const std::uint8_t* data, std::size_t length) {
|
||||||
std::uint16_t crc = kCrcInitial;
|
std::uint16_t crc = kCrcInitial;
|
||||||
for (std::size_t i = 0; i < length; ++i) {
|
for (std::size_t i = 0; i < length; ++i) {
|
||||||
@@ -37,7 +42,8 @@ std::uint16_t crc16_modbus(const std::uint8_t* data, std::size_t length) {
|
|||||||
for (int bit = 0; bit < 8; ++bit) {
|
for (int bit = 0; bit < 8; ++bit) {
|
||||||
if ((crc & 0x0001U) != 0U) {
|
if ((crc & 0x0001U) != 0U) {
|
||||||
crc = static_cast<std::uint16_t>((crc >> 1U) ^ kCrcPolynomial);
|
crc = static_cast<std::uint16_t>((crc >> 1U) ^ kCrcPolynomial);
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
crc = static_cast<std::uint16_t>(crc >> 1U);
|
crc = static_cast<std::uint16_t>(crc >> 1U);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,11 +77,10 @@ void tactile_close(CPCodecContext* ctx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int tactile_send_packet(CPCodecContext* ctx, const CPPacket& packet) {
|
int tactile_send_packet(CPCodecContext* ctx, const CPPacket& packet) {
|
||||||
auto* priv = get_priv(ctx);
|
auto priv = get_priv(ctx);
|
||||||
if (!priv) {
|
if (!priv) {
|
||||||
return CP_ERROR_INVALID_STATE;
|
return CP_ERROR_INVALID_STATE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.flush) {
|
if (packet.flush) {
|
||||||
priv->fifo.clear();
|
priv->fifo.clear();
|
||||||
priv->end_of_stream = false;
|
priv->end_of_stream = false;
|
||||||
@@ -93,14 +98,6 @@ int tactile_send_packet(CPCodecContext* ctx, const CPPacket& packet) {
|
|||||||
return CP_SUCCESS;
|
return CP_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t frame_length_from_payload(std::uint8_t payload_length) {
|
|
||||||
return 1U + 1U + 1U + 1U + payload_length + 2U + 2U;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::uint8_t* buffer_data(const std::vector<std::uint8_t>& buf) {
|
|
||||||
return buf.empty() ? nullptr : buf.data();
|
|
||||||
}
|
|
||||||
|
|
||||||
int tactile_receive_frame(CPCodecContext* ctx, CPFrame& frame) {
|
int tactile_receive_frame(CPCodecContext* ctx, CPFrame& frame) {
|
||||||
auto* priv = get_priv(ctx);
|
auto* priv = get_priv(ctx);
|
||||||
if (!priv) {
|
if (!priv) {
|
||||||
@@ -118,7 +115,6 @@ int tactile_receive_frame(CPCodecContext* ctx, CPFrame& frame) {
|
|||||||
return CP_ERROR_EAGAIN;
|
return CP_ERROR_EAGAIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discard bytes until start byte is found.
|
|
||||||
auto start_it = std::find(buf.begin(), buf.end(), kStartByte);
|
auto start_it = std::find(buf.begin(), buf.end(), kStartByte);
|
||||||
if (start_it == buf.end()) {
|
if (start_it == buf.end()) {
|
||||||
buf.clear();
|
buf.clear();
|
||||||
@@ -128,13 +124,13 @@ int tactile_receive_frame(CPCodecContext* ctx, CPFrame& frame) {
|
|||||||
}
|
}
|
||||||
return CP_ERROR_EAGAIN;
|
return CP_ERROR_EAGAIN;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start_it != buf.begin()) {
|
if (start_it != buf.begin()) {
|
||||||
buf.erase(buf.begin(), start_it);
|
buf.erase(buf.begin(), start_it);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buf.size() < kMinimumFrameSize) {
|
if (buf.size() < kMinimumFrameSize) {
|
||||||
if (priv->end_of_stream) {
|
if (priv->end_of_stream) {
|
||||||
// Incomplete frame at end of stream: drop it and report EOF.
|
|
||||||
buf.clear();
|
buf.clear();
|
||||||
priv->end_of_stream = false;
|
priv->end_of_stream = false;
|
||||||
return CP_ERROR_EOF;
|
return CP_ERROR_EOF;
|
||||||
@@ -143,14 +139,13 @@ int tactile_receive_frame(CPCodecContext* ctx, CPFrame& frame) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const std::uint8_t* data = buffer_data(buf);
|
const std::uint8_t* data = buffer_data(buf);
|
||||||
const std::uint8_t address = data[1];
|
const std::uint8_t address = data[1U];
|
||||||
const std::uint8_t function = data[2];
|
const FunctionCode function = static_cast<FunctionCode>(data[2U]);
|
||||||
const std::uint8_t payload_length = data[3];
|
const std::uint8_t payload_length = data[3U];
|
||||||
|
|
||||||
const std::size_t total_frame_length = frame_length_from_payload(payload_length);
|
const std::size_t total_frame_length = frame_length_from_payload(payload_length);
|
||||||
|
|
||||||
if (buf.size() < total_frame_length) {
|
if (buf.size() < total_frame_length) {
|
||||||
if (priv->end_of_stream) {
|
if (priv->end_of_stream) {
|
||||||
// Not enough data before stream end: treat as EOF and drop buffer.
|
|
||||||
buf.clear();
|
buf.clear();
|
||||||
priv->end_of_stream = false;
|
priv->end_of_stream = false;
|
||||||
return CP_ERROR_EOF;
|
return CP_ERROR_EOF;
|
||||||
@@ -171,12 +166,11 @@ int tactile_receive_frame(CPCodecContext* ctx, CPFrame& frame) {
|
|||||||
const std::uint8_t end_second = data[end_offset + 1U];
|
const std::uint8_t end_second = data[end_offset + 1U];
|
||||||
|
|
||||||
if (end_first != kEndByteFirst || end_second != kEndByteSecond) {
|
if (end_first != kEndByteFirst || end_second != kEndByteSecond) {
|
||||||
// Invalid end marker, drop start byte and retry.
|
|
||||||
buf.erase(buf.begin());
|
buf.erase(buf.begin());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::size_t crc_region_length = 3U + payload_length; // address + function + length + payload
|
const std::size_t crc_region_length = 3U + payload_length;
|
||||||
const std::uint16_t computed_crc = crc16_modbus(data + 1U, crc_region_length);
|
const std::uint16_t computed_crc = crc16_modbus(data + 1U, crc_region_length);
|
||||||
if (computed_crc != crc_value) {
|
if (computed_crc != crc_value) {
|
||||||
buf.erase(buf.begin());
|
buf.erase(buf.begin());
|
||||||
@@ -192,24 +186,22 @@ int tactile_receive_frame(CPCodecContext* ctx, CPFrame& frame) {
|
|||||||
frame.valid = true;
|
frame.valid = true;
|
||||||
|
|
||||||
buf.erase(buf.begin(), buf.begin() + static_cast<std::ptrdiff_t>(total_frame_length));
|
buf.erase(buf.begin(), buf.begin() + static_cast<std::ptrdiff_t>(total_frame_length));
|
||||||
|
|
||||||
return CP_SUCCESS;
|
return CP_SUCCESS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const CPCodec kTactileCodec {
|
const CPCodec kTactileCodec {
|
||||||
"tactile_serial",
|
.name = "tactile_serial",
|
||||||
"Framed tactile sensor serial protocol decoder",
|
.long_name = "Framed tactile sensor serial protocol decoder",
|
||||||
CPMediaType::Data,
|
.type = CPMediaType::Data,
|
||||||
CPCodecID::Tactile,
|
.id = CPCodecID::Tactile,
|
||||||
sizeof(TactileDecoderContext),
|
.priv_data_size = sizeof(TactileDecoderContext),
|
||||||
&tactile_init,
|
.init = &tactile_init,
|
||||||
&tactile_close,
|
.close = &tactile_close,
|
||||||
&tactile_send_packet,
|
.send_packet = &tactile_send_packet,
|
||||||
&tactile_receive_frame
|
.receive_frame = &tactile_receive_frame
|
||||||
};
|
};
|
||||||
|
}
|
||||||
} // namespace
|
|
||||||
|
|
||||||
std::optional<TactileFrame> parse_frame(const CPFrame& frame) {
|
std::optional<TactileFrame> parse_frame(const CPFrame& frame) {
|
||||||
if (!frame.valid || frame.data.size() < kMinimumFrameSize) {
|
if (!frame.valid || frame.data.size() < kMinimumFrameSize) {
|
||||||
@@ -221,6 +213,7 @@ std::optional<TactileFrame> parse_frame(const CPFrame& frame) {
|
|||||||
if (bytes[0] != kStartByte) {
|
if (bytes[0] != kStartByte) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bytes[size - 2] != kEndByteFirst || bytes[size - 1] != kEndByteSecond) {
|
if (bytes[size - 2] != kEndByteFirst || bytes[size - 1] != kEndByteSecond) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
@@ -233,7 +226,6 @@ std::optional<TactileFrame> parse_frame(const CPFrame& frame) {
|
|||||||
if (frame_length_from_payload(length) != size) {
|
if (frame_length_from_payload(length) != size) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::uint8_t address = bytes[1];
|
const std::uint8_t address = bytes[1];
|
||||||
const FunctionCode function = static_cast<FunctionCode>(bytes[2]);
|
const FunctionCode function = static_cast<FunctionCode>(bytes[2]);
|
||||||
const std::size_t payload_offset = 4U;
|
const std::size_t payload_offset = 4U;
|
||||||
@@ -271,10 +263,6 @@ std::optional<MatrixSize> parse_matrix_size_payload(const TactileFrame& frame) {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<MatrixSize> parse_matrix_coordinate_payload(const TactileFrame& frame) {
|
|
||||||
return parse_matrix_size_payload(frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
const CPCodec* tactile_codec() {
|
const CPCodec* tactile_codec() {
|
||||||
return &kTactileCodec;
|
return &kTactileCodec;
|
||||||
}
|
}
|
||||||
@@ -282,5 +270,4 @@ const CPCodec* tactile_codec() {
|
|||||||
void register_tactile_codec() {
|
void register_tactile_codec() {
|
||||||
cpcodec_register(&kTactileCodec);
|
cpcodec_register(&kTactileCodec);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} // namespace ffmsep::tactile
|
|
||||||
@@ -1,17 +1,11 @@
|
|||||||
//
|
|
||||||
// High level helpers for the tactile sensor binary protocol.
|
|
||||||
//
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../cpdecoder.hh"
|
#include "cpdecoder.hh"
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace ffmsep::tactile {
|
namespace ffmsep::tactile {
|
||||||
|
|
||||||
inline constexpr std::uint8_t kStartByte = 0x3A;
|
inline constexpr std::uint8_t kStartByte = 0x3A;
|
||||||
inline constexpr std::uint8_t kEndByteFirst = 0x0D;
|
inline constexpr std::uint8_t kEndByteFirst = 0x0D;
|
||||||
inline constexpr std::uint8_t kEndByteSecond = 0x0A;
|
inline constexpr std::uint8_t kEndByteSecond = 0x0A;
|
||||||
@@ -23,7 +17,7 @@ enum class FunctionCode : std::uint8_t {
|
|||||||
ReadTemperature = 0x03,
|
ReadTemperature = 0x03,
|
||||||
SetDeviceId = 0x51,
|
SetDeviceId = 0x51,
|
||||||
SetMatrixSize = 0x52,
|
SetMatrixSize = 0x52,
|
||||||
CalibrationMode = 0x53
|
CalibrationMode = 0x53,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MatrixSize {
|
struct MatrixSize {
|
||||||
@@ -41,9 +35,8 @@ struct TactileFrame {
|
|||||||
std::optional<TactileFrame> parse_frame(const CPFrame& frame);
|
std::optional<TactileFrame> parse_frame(const CPFrame& frame);
|
||||||
std::vector<std::uint16_t> parse_pressure_values(const TactileFrame& frame);
|
std::vector<std::uint16_t> parse_pressure_values(const TactileFrame& frame);
|
||||||
std::optional<MatrixSize> parse_matrix_size_payload(const TactileFrame& frame);
|
std::optional<MatrixSize> parse_matrix_size_payload(const TactileFrame& frame);
|
||||||
std::optional<MatrixSize> parse_matrix_coordinate_payload(const TactileFrame& frame);
|
std::optional<MatrixSize> parse_patrix_coordinate_payload(const TactileFrame& frame);
|
||||||
|
|
||||||
const CPCodec* tactile_codec();
|
const CPCodec* tactile_codec();
|
||||||
void register_tactile_codec();
|
void register_tactile_codec();
|
||||||
|
}
|
||||||
} // namespace ffmsep::tactile
|
|
||||||
@@ -70,24 +70,17 @@ static auto ComConfigComponent(ThemeManager& manager, auto&& callback) {
|
|||||||
slpro::LeadingIcon { material::icon::kArrowDropDown, material::regular::font},
|
slpro::LeadingIcon { material::icon::kArrowDropDown, material::regular::font},
|
||||||
slpro::IndexChanged {[&](auto& self){ qDebug() << self.currentIndex();}},
|
slpro::IndexChanged {[&](auto& self){ qDebug() << self.currentIndex();}},
|
||||||
slpro::LeadingText {"Baud"},
|
slpro::LeadingText {"Baud"},
|
||||||
// slpro::MutableItems {select_baud_context},
|
|
||||||
MutableForward {
|
MutableForward {
|
||||||
slpro::SelectItems {},
|
slpro::SelectItems {},
|
||||||
select_baud_context,
|
select_baud_context,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// lnpro::Item<MatSelect> {
|
|
||||||
// // slpro::ThemeManager {manager},
|
|
||||||
// // slpro::LeadingIcon {material::icon::kArrowDropDown, material::regular::font},
|
|
||||||
// // slpro::IndexChanged {}
|
|
||||||
// // }
|
|
||||||
lnpro::SpacingItem {20},
|
lnpro::SpacingItem {20},
|
||||||
lnpro::Item<IconButton> {
|
lnpro::Item<IconButton> {
|
||||||
ibpro::ThemeManager {manager},
|
ibpro::ThemeManager {manager},
|
||||||
ibpro::FixedSize {40, 40},
|
ibpro::FixedSize {40, 40},
|
||||||
ibpro::Color { IconButton::Color::TONAL },
|
ibpro::Color { IconButton::Color::TONAL },
|
||||||
ibpro::Font { material::kRoundSmallFont },
|
ibpro::Font { material::kRoundSmallFont },
|
||||||
// ibpro::FontIcon { material::icon::kFavorite },
|
|
||||||
ibpro::FontIcon { material::icon::kAddLink },
|
ibpro::FontIcon { material::icon::kAddLink },
|
||||||
ibpro::Clickable {[slogen_context] {
|
ibpro::Clickable {[slogen_context] {
|
||||||
constexpr auto random_slogen = [] {
|
constexpr auto random_slogen = [] {
|
||||||
@@ -113,7 +106,7 @@ static auto ComConfigComponent(ThemeManager& manager, auto&& callback) {
|
|||||||
ibpro::Font { material::kRoundSmallFont },
|
ibpro::Font { material::kRoundSmallFont },
|
||||||
ibpro::FontIcon { material::icon::kRefresh },
|
ibpro::FontIcon { material::icon::kRefresh },
|
||||||
ibpro::Clickable {[select_baud_context] {
|
ibpro::Clickable {[select_baud_context] {
|
||||||
// 定义两组不同的选项
|
|
||||||
static constexpr auto options_group1 = std::array {
|
static constexpr auto options_group1 = std::array {
|
||||||
"第一组选项1", "第一组选项2", "第一组选项3"
|
"第一组选项1", "第一组选项2", "第一组选项3"
|
||||||
};
|
};
|
||||||
@@ -121,7 +114,6 @@ static auto ComConfigComponent(ThemeManager& manager, auto&& callback) {
|
|||||||
"第二组选项A", "第二组选项B", "第二组选项C", "第二组选项D"
|
"第二组选项A", "第二组选项B", "第二组选项C", "第二组选项D"
|
||||||
};
|
};
|
||||||
|
|
||||||
// 随机选择一组选项
|
|
||||||
static std::random_device rd;
|
static std::random_device rd;
|
||||||
static std::mt19937 gen(rd());
|
static std::mt19937 gen(rd());
|
||||||
std::uniform_int_distribution<> dist(0, 1);
|
std::uniform_int_distribution<> dist(0, 1);
|
||||||
@@ -136,9 +128,6 @@ static auto ComConfigComponent(ThemeManager& manager, auto&& callback) {
|
|||||||
new_options << QString::fromUtf8(option);
|
new_options << QString::fromUtf8(option);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
qDebug() << new_options;
|
|
||||||
|
|
||||||
// 更新选项列表,MatSelect会自动刷新
|
|
||||||
*select_baud_context = new_options;
|
*select_baud_context = new_options;
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
|
|||||||
40
dlog/dlog.cc
Normal file
40
dlog/dlog.cc
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// Created by Lenn on 2025/10/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
#include "dlog.hh"
|
||||||
|
|
||||||
|
spdlog::level::level_enum DLog::level_ = spdlog::level::info;
|
||||||
|
|
||||||
|
void DLog::set_level(DLogLevel level) {
|
||||||
|
printf("SetLevel log_level:%d\n", level);
|
||||||
|
fflush(stdout);
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
case DLogLevel::TRACE:
|
||||||
|
level_ = spdlog::level::trace;
|
||||||
|
break;
|
||||||
|
case DLogLevel::DEBUG:
|
||||||
|
level_ = spdlog::level::debug;
|
||||||
|
break;
|
||||||
|
case DLogLevel::INFO:
|
||||||
|
level_ = spdlog::level::info;
|
||||||
|
break;
|
||||||
|
case DLogLevel::WARN:
|
||||||
|
level_ = spdlog::level::warn;
|
||||||
|
break;
|
||||||
|
case DLogLevel::ERR:
|
||||||
|
level_ = spdlog::level::err;
|
||||||
|
break;
|
||||||
|
case DLogLevel::CRITICAL:
|
||||||
|
level_ = spdlog::level::critical;
|
||||||
|
break;
|
||||||
|
case DLogLevel::OFF:
|
||||||
|
level_ = spdlog::level::off;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
level_ = spdlog::level::trace;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
81
dlog/dlog.hh
Normal file
81
dlog/dlog.hh
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
//
|
||||||
|
// Created by Lenn on 2025/10/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef TOUCHSENSOR_DLOG_HH
|
||||||
|
#define TOUCHSENSOR_DLOG_HH
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <chrono>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <spdlog/sinks/daily_file_sink.h>
|
||||||
|
#include <spdlog/async.h>
|
||||||
|
#include <spdlog/sinks/stdout_color_sinks.h>
|
||||||
|
|
||||||
|
#ifndef SPDLOG_TRACE_ON
|
||||||
|
#define SPDLOG_TRACE_ON
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SPDLOG_DEBUG_ON
|
||||||
|
#define SPDLOG_DEBUG_ON
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE
|
||||||
|
|
||||||
|
enum class DLogLevel {
|
||||||
|
TRACE,
|
||||||
|
DEBUG,
|
||||||
|
INFO,
|
||||||
|
WARN,
|
||||||
|
ERR,
|
||||||
|
CRITICAL,
|
||||||
|
OFF
|
||||||
|
};
|
||||||
|
|
||||||
|
class DLog{
|
||||||
|
|
||||||
|
public:
|
||||||
|
static DLog* get_instance() {
|
||||||
|
static DLog dlogger;
|
||||||
|
return &dlogger;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<spdlog::logger> get_logger() {
|
||||||
|
return log_;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void set_level(DLogLevel level);
|
||||||
|
private:
|
||||||
|
DLog() {
|
||||||
|
std::vector<spdlog::sink_ptr> sinks;
|
||||||
|
auto consolesink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
|
||||||
|
consolesink->set_level(level_);
|
||||||
|
consolesink->set_pattern("[%Y-%m-%d %H:%M:%S.%e][thread %t][%@,%!][%l] : %v");
|
||||||
|
sinks.push_back(consolesink);
|
||||||
|
|
||||||
|
auto dailysink = std::make_shared<spdlog::sinks::daily_file_format_sink_mt>("logs/daily.log", 23, 59);
|
||||||
|
dailysink->set_level(level_);
|
||||||
|
dailysink->set_pattern("[%Y-%m-%d %H:%M:%S.%e][thread %t][%@,%!][%l] : %v");
|
||||||
|
sinks.push_back(dailysink);
|
||||||
|
|
||||||
|
log_ = std::make_shared<spdlog::logger>("both", std::begin(sinks), std::end(sinks));
|
||||||
|
spdlog::register_logger(log_);
|
||||||
|
// spdlog::flush_every(std::chrono::seconds(1));
|
||||||
|
spdlog::flush_every(std::chrono::seconds(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
~DLog() = default;
|
||||||
|
std::shared_ptr<spdlog::logger> log_;
|
||||||
|
static spdlog::level::level_enum level_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#define LogTrace(...) SPDLOG_LOGGER_CALL(DLog::get_instance()->get_logger().get(), spdlog::level::trace, __VA_ARGS__);
|
||||||
|
#define LogDebug(...) SPDLOG_LOGGER_CALL(DLog::get_instance()->get_logger().get(), spdlog::level::debug, __VA_ARGS__);
|
||||||
|
#define LogInfo(...) SPDLOG_LOGGER_CALL(DLog::get_instance()->get_logger().get(), spdlog::level::info, __VA_ARGS__);
|
||||||
|
#define LogWarn(...) SPDLOG_LOGGER_CALL(DLog::get_instance()->get_logger().get(), spdlog::level::warn, __VA_ARGS__);
|
||||||
|
#define LogErr(...) SPDLOG_LOGGER_CALL(DLog::get_instance()->get_logger().get(), spdlog::level::err, __VA_ARGS__);
|
||||||
|
#define LogCritical(...) SPDLOG_LOGGER_CALL(DLog::get_instance()->get_logger().get(), spdlog::level::critical, __VA_ARGS__);
|
||||||
|
#define LogOff(...) SPDLOG_LOGGER_CALL(DLog::get_instance()->get_logger().get(), spdlog::level::off, __VA_ARGS__);
|
||||||
|
|
||||||
|
#endif //TOUCHSENSOR_DLOG_HH
|
||||||
120
examples/cpstream_demo.cc
Normal file
120
examples/cpstream_demo.cc
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <csignal>
|
||||||
|
#include <iostream>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "components/ffmsep/cpstream_core.hh"
|
||||||
|
#include "components/ffmsep/tactile/tacdec.hh"
|
||||||
|
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
static volatile std::sig_atomic_t g_running = 1;
|
||||||
|
|
||||||
|
void handle_sigint(int) {
|
||||||
|
g_running = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
std::signal(SIGINT, handle_sigint);
|
||||||
|
|
||||||
|
// Register tactile codec so the stream can resolve it by ID or name.
|
||||||
|
ffmsep::tactile::register_tactile_codec();
|
||||||
|
|
||||||
|
const auto ports = ffmsep::CPStreamCore::list_available_ports();
|
||||||
|
const auto describe_ports = [&ports]() {
|
||||||
|
if (ports.empty()) {
|
||||||
|
std::cout << "No serial ports detected.\n";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::cout << "Available serial ports:\n";
|
||||||
|
for (const auto& p : ports) {
|
||||||
|
std::cout << " - " << p.port.c_str() << " (" << p.description.c_str() << ")\n";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string port;
|
||||||
|
if (argc >= 2) {
|
||||||
|
const std::string requested = argv[1];
|
||||||
|
if (ports.empty()) {
|
||||||
|
std::cout << "No serial ports detected, attempting requested '" << requested << "'.\n";
|
||||||
|
port = requested;
|
||||||
|
} else {
|
||||||
|
const auto it = std::find_if(ports.begin(), ports.end(),
|
||||||
|
[&](const auto& info) { return info.port == requested; });
|
||||||
|
if (it == ports.end()) {
|
||||||
|
std::cerr << "Requested port '" << requested << "' not found among detected ports.\n";
|
||||||
|
describe_ports();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
port = it->port;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
describe_ports();
|
||||||
|
if (!ports.empty()) {
|
||||||
|
port = ports.front().port;
|
||||||
|
std::cout << "Auto-selecting: " << port << "\n";
|
||||||
|
} else {
|
||||||
|
std::cerr << "No serial ports found. Exiting.\n";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ffmsep::CPStreamConfig cfg;
|
||||||
|
cfg.port = port;
|
||||||
|
cfg.baudrate = 115200;
|
||||||
|
cfg.codec_id = ffmsep::CPCodecID::Tactile; // resolve tactile decoder
|
||||||
|
cfg.read_chunk_size = 256;
|
||||||
|
cfg.packet_queue_capacity = 128;
|
||||||
|
cfg.frame_queue_capacity = 32;
|
||||||
|
|
||||||
|
ffmsep::CPStreamCore core(cfg);
|
||||||
|
if (!core.open()) {
|
||||||
|
std::cerr << "Open failed: " << core.last_error() << "\n";
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: receive frames via callback
|
||||||
|
core.set_frame_callback([](const ffmsep::DecodedFrame& df) {
|
||||||
|
const auto maybe = ffmsep::tactile::parse_frame(df.frame);
|
||||||
|
if (!maybe) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& tf = *maybe;
|
||||||
|
const auto pv = ffmsep::tactile::parse_pressure_values(tf);
|
||||||
|
std::cout << "Frame pts=" << df.pts
|
||||||
|
<< " addr=" << int(tf.device_address)
|
||||||
|
<< " func=" << int(static_cast<std::uint8_t>(tf.function))
|
||||||
|
<< " len=" << int(tf.data_length)
|
||||||
|
<< " pressures=" << pv.size() << "\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!core.start()) {
|
||||||
|
std::cerr << "Start failed: " << core.last_error() << "\n";
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "Streaming from " << port << ". Press Ctrl+C to stop...\n";
|
||||||
|
|
||||||
|
// Also demonstrate polling API (in case users don't want callbacks)
|
||||||
|
while (g_running) {
|
||||||
|
ffmsep::DecodedFrame df;
|
||||||
|
if (core.wait_for_frame(df, 200ms)) {
|
||||||
|
const auto maybe = ffmsep::tactile::parse_frame(df.frame);
|
||||||
|
if (maybe) {
|
||||||
|
const auto sz = ffmsep::tactile::parse_matrix_size_payload(*maybe);
|
||||||
|
if (sz) {
|
||||||
|
std::cout << " matrix=" << int(sz->long_edge) << "x" << int(sz->short_edge) << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
core.stop();
|
||||||
|
core.close();
|
||||||
|
std::cout << "Stopped.\n";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -16,7 +16,6 @@ struct PainterResource : public QPixmap {
|
|||||||
: QPixmap {} {
|
: QPixmap {} {
|
||||||
const auto qurl = QUrl(QString::fromUtf8(url.data(), static_cast<int>(url.size())));
|
const auto qurl = QUrl(QString::fromUtf8(url.data(), static_cast<int>(url.size())));
|
||||||
if (is_filesystem_url(url) || is_qt_resource_url(url)) {
|
if (is_filesystem_url(url) || is_qt_resource_url(url)) {
|
||||||
qDebug() << "[PainterResource] is_filesystem_url" << url;
|
|
||||||
QPixmap::load(qurl.path());
|
QPixmap::load(qurl.path());
|
||||||
} else if (is_network_url(url)) {
|
} else if (is_network_url(url)) {
|
||||||
download_resource_from_network(qurl, [](auto&) { });
|
download_resource_from_network(qurl, [](auto&) { });
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
project(serial)
|
|
||||||
|
|
||||||
if(APPLE)
|
|
||||||
find_library(IOKIT_LIBRARY IOKit)
|
|
||||||
find_library(FOUNDATION_LIBRARY Foundation)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(UNIX AND NOT APPLE)
|
|
||||||
# If Linux, add rt and pthread
|
|
||||||
set(rt_LIBRARIES rt)
|
|
||||||
set(pthread_LIBRARIES pthread)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
## Sources
|
|
||||||
set(serial_SRCS
|
|
||||||
src/serial.cc
|
|
||||||
include/serial/serial.h
|
|
||||||
include/serial/v8stdint.h
|
|
||||||
)
|
|
||||||
|
|
||||||
if(APPLE)
|
|
||||||
# If OSX
|
|
||||||
list(APPEND serial_SRCS src/impl/unix.cc)
|
|
||||||
list(APPEND serial_SRCS src/impl/list_ports/list_ports_osx.cc)
|
|
||||||
elseif(UNIX)
|
|
||||||
# If unix
|
|
||||||
list(APPEND serial_SRCS src/impl/unix.cc)
|
|
||||||
list(APPEND serial_SRCS src/impl/list_ports/list_ports_linux.cc)
|
|
||||||
else()
|
|
||||||
# If windows
|
|
||||||
list(APPEND serial_SRCS src/impl/win.cc)
|
|
||||||
list(APPEND serial_SRCS src/impl/list_ports/list_ports_win.cc)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
## Add serial library
|
|
||||||
add_library(${PROJECT_NAME} ${serial_SRCS})
|
|
||||||
if(APPLE)
|
|
||||||
target_link_libraries(${PROJECT_NAME} ${FOUNDATION_LIBRARY} ${IOKIT_LIBRARY})
|
|
||||||
elseif(UNIX)
|
|
||||||
target_link_libraries(${PROJECT_NAME} rt pthread)
|
|
||||||
else()
|
|
||||||
target_link_libraries(${PROJECT_NAME} setupapi)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set_target_properties(serial PROPERTIES
|
|
||||||
AUTOMOC OFF
|
|
||||||
AUTORCC OFF
|
|
||||||
AUTOUIC OFF
|
|
||||||
)
|
|
||||||
## Include headers
|
|
||||||
include_directories(include)
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
/***
|
|
||||||
* This example expects the serial port has a loopback on it.
|
|
||||||
*
|
|
||||||
* Alternatively, you could use an Arduino:
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* void setup() {
|
|
||||||
* Serial.begin(<insert your baudrate here>);
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* void loop() {
|
|
||||||
* if (Serial.available()) {
|
|
||||||
* Serial.write(Serial.read());
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <string>
|
|
||||||
#include <iostream>
|
|
||||||
#include <cstdio>
|
|
||||||
|
|
||||||
// OS Specific sleep
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#else
|
|
||||||
#include <unistd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "serial/serial.h"
|
|
||||||
|
|
||||||
using std::string;
|
|
||||||
using std::exception;
|
|
||||||
using std::cout;
|
|
||||||
using std::cerr;
|
|
||||||
using std::endl;
|
|
||||||
using std::vector;
|
|
||||||
|
|
||||||
void my_sleep(unsigned long milliseconds) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
Sleep(milliseconds); // 100 ms
|
|
||||||
#else
|
|
||||||
usleep(milliseconds*1000); // 100 ms
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void enumerate_ports()
|
|
||||||
{
|
|
||||||
vector<serial::PortInfo> devices_found = serial::list_ports();
|
|
||||||
|
|
||||||
vector<serial::PortInfo>::iterator iter = devices_found.begin();
|
|
||||||
|
|
||||||
while( iter != devices_found.end() )
|
|
||||||
{
|
|
||||||
serial::PortInfo device = *iter++;
|
|
||||||
|
|
||||||
printf( "(%s, %s, %s)\n", device.port.c_str(), device.description.c_str(),
|
|
||||||
device.hardware_id.c_str() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void print_usage()
|
|
||||||
{
|
|
||||||
cerr << "Usage: test_serial {-e|<serial port address>} ";
|
|
||||||
cerr << "<baudrate> [test string]" << endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
int run(int argc, char **argv)
|
|
||||||
{
|
|
||||||
if(argc < 2) {
|
|
||||||
print_usage();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Argument 1 is the serial port or enumerate flag
|
|
||||||
string port(argv[1]);
|
|
||||||
|
|
||||||
if( port == "-e" ) {
|
|
||||||
enumerate_ports();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
else if( argc < 3 ) {
|
|
||||||
print_usage();
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Argument 2 is the baudrate
|
|
||||||
unsigned long baud = 0;
|
|
||||||
#if defined(WIN32) && !defined(__MINGW32__)
|
|
||||||
sscanf_s(argv[2], "%lu", &baud);
|
|
||||||
#else
|
|
||||||
sscanf(argv[2], "%lu", &baud);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// port, baudrate, timeout in milliseconds
|
|
||||||
serial::Serial my_serial(port, baud, serial::Timeout::simpleTimeout(1000));
|
|
||||||
|
|
||||||
cout << "Is the serial port open?";
|
|
||||||
if(my_serial.isOpen())
|
|
||||||
cout << " Yes." << endl;
|
|
||||||
else
|
|
||||||
cout << " No." << endl;
|
|
||||||
|
|
||||||
// Get the Test string
|
|
||||||
int count = 0;
|
|
||||||
string test_string;
|
|
||||||
if (argc == 4) {
|
|
||||||
test_string = argv[3];
|
|
||||||
} else {
|
|
||||||
test_string = "Testing.";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the timeout, there should be 1 second between prints
|
|
||||||
cout << "Timeout == 1000ms, asking for 1 more byte than written." << endl;
|
|
||||||
while (count < 10) {
|
|
||||||
size_t bytes_wrote = my_serial.write(test_string);
|
|
||||||
|
|
||||||
string result = my_serial.read(test_string.length()+1);
|
|
||||||
|
|
||||||
cout << "Iteration: " << count << ", Bytes written: ";
|
|
||||||
cout << bytes_wrote << ", Bytes read: ";
|
|
||||||
cout << result.length() << ", String read: " << result << endl;
|
|
||||||
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the timeout at 250ms
|
|
||||||
my_serial.setTimeout(serial::Timeout::max(), 250, 0, 250, 0);
|
|
||||||
count = 0;
|
|
||||||
cout << "Timeout == 250ms, asking for 1 more byte than written." << endl;
|
|
||||||
while (count < 10) {
|
|
||||||
size_t bytes_wrote = my_serial.write(test_string);
|
|
||||||
|
|
||||||
string result = my_serial.read(test_string.length()+1);
|
|
||||||
|
|
||||||
cout << "Iteration: " << count << ", Bytes written: ";
|
|
||||||
cout << bytes_wrote << ", Bytes read: ";
|
|
||||||
cout << result.length() << ", String read: " << result << endl;
|
|
||||||
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the timeout at 250ms, but asking exactly for what was written
|
|
||||||
count = 0;
|
|
||||||
cout << "Timeout == 250ms, asking for exactly what was written." << endl;
|
|
||||||
while (count < 10) {
|
|
||||||
size_t bytes_wrote = my_serial.write(test_string);
|
|
||||||
|
|
||||||
string result = my_serial.read(test_string.length());
|
|
||||||
|
|
||||||
cout << "Iteration: " << count << ", Bytes written: ";
|
|
||||||
cout << bytes_wrote << ", Bytes read: ";
|
|
||||||
cout << result.length() << ", String read: " << result << endl;
|
|
||||||
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test the timeout at 250ms, but asking for 1 less than what was written
|
|
||||||
count = 0;
|
|
||||||
cout << "Timeout == 250ms, asking for 1 less than was written." << endl;
|
|
||||||
while (count < 10) {
|
|
||||||
size_t bytes_wrote = my_serial.write(test_string);
|
|
||||||
|
|
||||||
string result = my_serial.read(test_string.length()-1);
|
|
||||||
|
|
||||||
cout << "Iteration: " << count << ", Bytes written: ";
|
|
||||||
cout << bytes_wrote << ", Bytes read: ";
|
|
||||||
cout << result.length() << ", String read: " << result << endl;
|
|
||||||
|
|
||||||
count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char **argv) {
|
|
||||||
try {
|
|
||||||
return run(argc, argv);
|
|
||||||
} catch (exception &e) {
|
|
||||||
cerr << "Unhandled Exception: " << e.what() << endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
/*!
|
|
||||||
* \file serial/impl/unix.h
|
|
||||||
* \author William Woodall <wjwwood@gmail.com>
|
|
||||||
* \author John Harrison <ash@greaterthaninfinity.com>
|
|
||||||
* \version 0.1
|
|
||||||
*
|
|
||||||
* \section LICENSE
|
|
||||||
*
|
|
||||||
* The MIT License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2012 William Woodall, John Harrison
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
* copy of this software and associated documentation files (the "Software"),
|
|
||||||
* to deal in the Software without restriction, including without limitation
|
|
||||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
* and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
* Software is furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
* DEALINGS IN THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* \section DESCRIPTION
|
|
||||||
*
|
|
||||||
* This provides a unix based pimpl for the Serial class. This implementation is
|
|
||||||
* based off termios.h and uses select for multiplexing the IO ports.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#if !defined(_WIN32)
|
|
||||||
|
|
||||||
#ifndef SERIAL_IMPL_UNIX_H
|
|
||||||
#define SERIAL_IMPL_UNIX_H
|
|
||||||
|
|
||||||
#include "serial/serial.h"
|
|
||||||
|
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
namespace serial {
|
|
||||||
|
|
||||||
using std::size_t;
|
|
||||||
using std::string;
|
|
||||||
using std::invalid_argument;
|
|
||||||
|
|
||||||
using serial::SerialException;
|
|
||||||
using serial::IOException;
|
|
||||||
|
|
||||||
class MillisecondTimer {
|
|
||||||
public:
|
|
||||||
MillisecondTimer(const uint32_t millis);
|
|
||||||
int64_t remaining();
|
|
||||||
|
|
||||||
private:
|
|
||||||
static timespec timespec_now();
|
|
||||||
timespec expiry;
|
|
||||||
};
|
|
||||||
|
|
||||||
class serial::Serial::SerialImpl {
|
|
||||||
public:
|
|
||||||
SerialImpl (const string &port,
|
|
||||||
unsigned long baudrate,
|
|
||||||
bytesize_t bytesize,
|
|
||||||
parity_t parity,
|
|
||||||
stopbits_t stopbits,
|
|
||||||
flowcontrol_t flowcontrol);
|
|
||||||
|
|
||||||
virtual ~SerialImpl ();
|
|
||||||
|
|
||||||
void
|
|
||||||
open ();
|
|
||||||
|
|
||||||
void
|
|
||||||
close ();
|
|
||||||
|
|
||||||
bool
|
|
||||||
isOpen () const;
|
|
||||||
|
|
||||||
size_t
|
|
||||||
available ();
|
|
||||||
|
|
||||||
bool
|
|
||||||
waitReadable (uint32_t timeout);
|
|
||||||
|
|
||||||
void
|
|
||||||
waitByteTimes (size_t count);
|
|
||||||
|
|
||||||
size_t
|
|
||||||
read (uint8_t *buf, size_t size = 1);
|
|
||||||
|
|
||||||
size_t
|
|
||||||
write (const uint8_t *data, size_t length);
|
|
||||||
|
|
||||||
void
|
|
||||||
flush ();
|
|
||||||
|
|
||||||
void
|
|
||||||
flushInput ();
|
|
||||||
|
|
||||||
void
|
|
||||||
flushOutput ();
|
|
||||||
|
|
||||||
void
|
|
||||||
sendBreak (int duration);
|
|
||||||
|
|
||||||
void
|
|
||||||
setBreak (bool level);
|
|
||||||
|
|
||||||
void
|
|
||||||
setRTS (bool level);
|
|
||||||
|
|
||||||
void
|
|
||||||
setDTR (bool level);
|
|
||||||
|
|
||||||
bool
|
|
||||||
waitForChange ();
|
|
||||||
|
|
||||||
bool
|
|
||||||
getCTS ();
|
|
||||||
|
|
||||||
bool
|
|
||||||
getDSR ();
|
|
||||||
|
|
||||||
bool
|
|
||||||
getRI ();
|
|
||||||
|
|
||||||
bool
|
|
||||||
getCD ();
|
|
||||||
|
|
||||||
void
|
|
||||||
setPort (const string &port);
|
|
||||||
|
|
||||||
string
|
|
||||||
getPort () const;
|
|
||||||
|
|
||||||
void
|
|
||||||
setTimeout (Timeout &timeout);
|
|
||||||
|
|
||||||
Timeout
|
|
||||||
getTimeout () const;
|
|
||||||
|
|
||||||
void
|
|
||||||
setBaudrate (unsigned long baudrate);
|
|
||||||
|
|
||||||
unsigned long
|
|
||||||
getBaudrate () const;
|
|
||||||
|
|
||||||
void
|
|
||||||
setBytesize (bytesize_t bytesize);
|
|
||||||
|
|
||||||
bytesize_t
|
|
||||||
getBytesize () const;
|
|
||||||
|
|
||||||
void
|
|
||||||
setParity (parity_t parity);
|
|
||||||
|
|
||||||
parity_t
|
|
||||||
getParity () const;
|
|
||||||
|
|
||||||
void
|
|
||||||
setStopbits (stopbits_t stopbits);
|
|
||||||
|
|
||||||
stopbits_t
|
|
||||||
getStopbits () const;
|
|
||||||
|
|
||||||
void
|
|
||||||
setFlowcontrol (flowcontrol_t flowcontrol);
|
|
||||||
|
|
||||||
flowcontrol_t
|
|
||||||
getFlowcontrol () const;
|
|
||||||
|
|
||||||
void
|
|
||||||
readLock ();
|
|
||||||
|
|
||||||
void
|
|
||||||
readUnlock ();
|
|
||||||
|
|
||||||
void
|
|
||||||
writeLock ();
|
|
||||||
|
|
||||||
void
|
|
||||||
writeUnlock ();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void reconfigurePort ();
|
|
||||||
|
|
||||||
private:
|
|
||||||
string port_; // Path to the file descriptor
|
|
||||||
int fd_; // The current file descriptor
|
|
||||||
|
|
||||||
bool is_open_;
|
|
||||||
bool xonxoff_;
|
|
||||||
bool rtscts_;
|
|
||||||
|
|
||||||
Timeout timeout_; // Timeout for read operations
|
|
||||||
unsigned long baudrate_; // Baudrate
|
|
||||||
uint32_t byte_time_ns_; // Nanoseconds to transmit/receive a single byte
|
|
||||||
|
|
||||||
parity_t parity_; // Parity
|
|
||||||
bytesize_t bytesize_; // Size of the bytes
|
|
||||||
stopbits_t stopbits_; // Stop Bits
|
|
||||||
flowcontrol_t flowcontrol_; // Flow Control
|
|
||||||
|
|
||||||
// Mutex used to lock the read functions
|
|
||||||
pthread_mutex_t read_mutex;
|
|
||||||
// Mutex used to lock the write functions
|
|
||||||
pthread_mutex_t write_mutex;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // SERIAL_IMPL_UNIX_H
|
|
||||||
|
|
||||||
#endif // !defined(_WIN32)
|
|
||||||
@@ -1,207 +0,0 @@
|
|||||||
/*!
|
|
||||||
* \file serial/impl/windows.h
|
|
||||||
* \author William Woodall <wjwwood@gmail.com>
|
|
||||||
* \author John Harrison <ash@greaterthaninfinity.com>
|
|
||||||
* \version 0.1
|
|
||||||
*
|
|
||||||
* \section LICENSE
|
|
||||||
*
|
|
||||||
* The MIT License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2012 William Woodall, John Harrison
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
* copy of this software and associated documentation files (the "Software"),
|
|
||||||
* to deal in the Software without restriction, including without limitation
|
|
||||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
* and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
* Software is furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
* DEALINGS IN THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* \section DESCRIPTION
|
|
||||||
*
|
|
||||||
* This provides a windows implementation of the Serial class interface.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#if defined(_WIN32)
|
|
||||||
|
|
||||||
#ifndef SERIAL_IMPL_WINDOWS_H
|
|
||||||
#define SERIAL_IMPL_WINDOWS_H
|
|
||||||
|
|
||||||
#include "serial/serial.h"
|
|
||||||
|
|
||||||
#include "windows.h"
|
|
||||||
|
|
||||||
namespace serial {
|
|
||||||
|
|
||||||
using std::string;
|
|
||||||
using std::wstring;
|
|
||||||
using std::invalid_argument;
|
|
||||||
|
|
||||||
using serial::SerialException;
|
|
||||||
using serial::IOException;
|
|
||||||
|
|
||||||
class serial::Serial::SerialImpl {
|
|
||||||
public:
|
|
||||||
SerialImpl (const string &port,
|
|
||||||
unsigned long baudrate,
|
|
||||||
bytesize_t bytesize,
|
|
||||||
parity_t parity,
|
|
||||||
stopbits_t stopbits,
|
|
||||||
flowcontrol_t flowcontrol);
|
|
||||||
|
|
||||||
virtual ~SerialImpl ();
|
|
||||||
|
|
||||||
void
|
|
||||||
open ();
|
|
||||||
|
|
||||||
void
|
|
||||||
close ();
|
|
||||||
|
|
||||||
bool
|
|
||||||
isOpen () const;
|
|
||||||
|
|
||||||
size_t
|
|
||||||
available ();
|
|
||||||
|
|
||||||
bool
|
|
||||||
waitReadable (uint32_t timeout);
|
|
||||||
|
|
||||||
void
|
|
||||||
waitByteTimes (size_t count);
|
|
||||||
|
|
||||||
size_t
|
|
||||||
read (uint8_t *buf, size_t size = 1);
|
|
||||||
|
|
||||||
size_t
|
|
||||||
write (const uint8_t *data, size_t length);
|
|
||||||
|
|
||||||
void
|
|
||||||
flush ();
|
|
||||||
|
|
||||||
void
|
|
||||||
flushInput ();
|
|
||||||
|
|
||||||
void
|
|
||||||
flushOutput ();
|
|
||||||
|
|
||||||
void
|
|
||||||
sendBreak (int duration);
|
|
||||||
|
|
||||||
void
|
|
||||||
setBreak (bool level);
|
|
||||||
|
|
||||||
void
|
|
||||||
setRTS (bool level);
|
|
||||||
|
|
||||||
void
|
|
||||||
setDTR (bool level);
|
|
||||||
|
|
||||||
bool
|
|
||||||
waitForChange ();
|
|
||||||
|
|
||||||
bool
|
|
||||||
getCTS ();
|
|
||||||
|
|
||||||
bool
|
|
||||||
getDSR ();
|
|
||||||
|
|
||||||
bool
|
|
||||||
getRI ();
|
|
||||||
|
|
||||||
bool
|
|
||||||
getCD ();
|
|
||||||
|
|
||||||
void
|
|
||||||
setPort (const string &port);
|
|
||||||
|
|
||||||
string
|
|
||||||
getPort () const;
|
|
||||||
|
|
||||||
void
|
|
||||||
setTimeout (Timeout &timeout);
|
|
||||||
|
|
||||||
Timeout
|
|
||||||
getTimeout () const;
|
|
||||||
|
|
||||||
void
|
|
||||||
setBaudrate (unsigned long baudrate);
|
|
||||||
|
|
||||||
unsigned long
|
|
||||||
getBaudrate () const;
|
|
||||||
|
|
||||||
void
|
|
||||||
setBytesize (bytesize_t bytesize);
|
|
||||||
|
|
||||||
bytesize_t
|
|
||||||
getBytesize () const;
|
|
||||||
|
|
||||||
void
|
|
||||||
setParity (parity_t parity);
|
|
||||||
|
|
||||||
parity_t
|
|
||||||
getParity () const;
|
|
||||||
|
|
||||||
void
|
|
||||||
setStopbits (stopbits_t stopbits);
|
|
||||||
|
|
||||||
stopbits_t
|
|
||||||
getStopbits () const;
|
|
||||||
|
|
||||||
void
|
|
||||||
setFlowcontrol (flowcontrol_t flowcontrol);
|
|
||||||
|
|
||||||
flowcontrol_t
|
|
||||||
getFlowcontrol () const;
|
|
||||||
|
|
||||||
void
|
|
||||||
readLock ();
|
|
||||||
|
|
||||||
void
|
|
||||||
readUnlock ();
|
|
||||||
|
|
||||||
void
|
|
||||||
writeLock ();
|
|
||||||
|
|
||||||
void
|
|
||||||
writeUnlock ();
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void reconfigurePort ();
|
|
||||||
|
|
||||||
private:
|
|
||||||
wstring port_; // Path to the file descriptor
|
|
||||||
HANDLE fd_;
|
|
||||||
|
|
||||||
bool is_open_;
|
|
||||||
|
|
||||||
Timeout timeout_; // Timeout for read operations
|
|
||||||
unsigned long baudrate_; // Baudrate
|
|
||||||
|
|
||||||
parity_t parity_; // Parity
|
|
||||||
bytesize_t bytesize_; // Size of the bytes
|
|
||||||
stopbits_t stopbits_; // Stop Bits
|
|
||||||
flowcontrol_t flowcontrol_; // Flow Control
|
|
||||||
|
|
||||||
// Mutex used to lock the read functions
|
|
||||||
HANDLE read_mutex;
|
|
||||||
// Mutex used to lock the write functions
|
|
||||||
HANDLE write_mutex;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // SERIAL_IMPL_WINDOWS_H
|
|
||||||
|
|
||||||
#endif // if defined(_WIN32)
|
|
||||||
@@ -1,775 +0,0 @@
|
|||||||
/*!
|
|
||||||
* \file serial/serial.h
|
|
||||||
* \author William Woodall <wjwwood@gmail.com>
|
|
||||||
* \author John Harrison <ash.gti@gmail.com>
|
|
||||||
* \version 0.1
|
|
||||||
*
|
|
||||||
* \section LICENSE
|
|
||||||
*
|
|
||||||
* The MIT License
|
|
||||||
*
|
|
||||||
* Copyright (c) 2012 William Woodall
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
* copy of this software and associated documentation files (the "Software"),
|
|
||||||
* to deal in the Software without restriction, including without limitation
|
|
||||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
||||||
* and/or sell copies of the Software, and to permit persons to whom the
|
|
||||||
* Software is furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in
|
|
||||||
* all copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
||||||
* DEALINGS IN THE SOFTWARE.
|
|
||||||
*
|
|
||||||
* \section DESCRIPTION
|
|
||||||
*
|
|
||||||
* This provides a cross platform interface for interacting with Serial Ports.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef SERIAL_H
|
|
||||||
#define SERIAL_H
|
|
||||||
|
|
||||||
#include <limits>
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
#include <cstring>
|
|
||||||
#include <sstream>
|
|
||||||
#include <exception>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <serial/v8stdint.h>
|
|
||||||
|
|
||||||
#define THROW(exceptionClass, message) throw exceptionClass(__FILE__, \
|
|
||||||
__LINE__, (message) )
|
|
||||||
|
|
||||||
namespace serial {
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Enumeration defines the possible bytesizes for the serial port.
|
|
||||||
*/
|
|
||||||
typedef enum {
|
|
||||||
fivebits = 5,
|
|
||||||
sixbits = 6,
|
|
||||||
sevenbits = 7,
|
|
||||||
eightbits = 8
|
|
||||||
} bytesize_t;
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Enumeration defines the possible parity types for the serial port.
|
|
||||||
*/
|
|
||||||
typedef enum {
|
|
||||||
parity_none = 0,
|
|
||||||
parity_odd = 1,
|
|
||||||
parity_even = 2,
|
|
||||||
parity_mark = 3,
|
|
||||||
parity_space = 4
|
|
||||||
} parity_t;
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Enumeration defines the possible stopbit types for the serial port.
|
|
||||||
*/
|
|
||||||
typedef enum {
|
|
||||||
stopbits_one = 1,
|
|
||||||
stopbits_two = 2,
|
|
||||||
stopbits_one_point_five
|
|
||||||
} stopbits_t;
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Enumeration defines the possible flowcontrol types for the serial port.
|
|
||||||
*/
|
|
||||||
typedef enum {
|
|
||||||
flowcontrol_none = 0,
|
|
||||||
flowcontrol_software,
|
|
||||||
flowcontrol_hardware
|
|
||||||
} flowcontrol_t;
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Structure for setting the timeout of the serial port, times are
|
|
||||||
* in milliseconds.
|
|
||||||
*
|
|
||||||
* In order to disable the interbyte timeout, set it to Timeout::max().
|
|
||||||
*/
|
|
||||||
struct Timeout {
|
|
||||||
#ifdef max
|
|
||||||
# undef max
|
|
||||||
#endif
|
|
||||||
static uint32_t max() {return std::numeric_limits<uint32_t>::max();}
|
|
||||||
/*!
|
|
||||||
* Convenience function to generate Timeout structs using a
|
|
||||||
* single absolute timeout.
|
|
||||||
*
|
|
||||||
* \param timeout A long that defines the time in milliseconds until a
|
|
||||||
* timeout occurs after a call to read or write is made.
|
|
||||||
*
|
|
||||||
* \return Timeout struct that represents this simple timeout provided.
|
|
||||||
*/
|
|
||||||
static Timeout simpleTimeout(uint32_t timeout) {
|
|
||||||
return Timeout(max(), timeout, 0, timeout, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! Number of milliseconds between bytes received to timeout on. */
|
|
||||||
uint32_t inter_byte_timeout;
|
|
||||||
/*! A constant number of milliseconds to wait after calling read. */
|
|
||||||
uint32_t read_timeout_constant;
|
|
||||||
/*! A multiplier against the number of requested bytes to wait after
|
|
||||||
* calling read.
|
|
||||||
*/
|
|
||||||
uint32_t read_timeout_multiplier;
|
|
||||||
/*! A constant number of milliseconds to wait after calling write. */
|
|
||||||
uint32_t write_timeout_constant;
|
|
||||||
/*! A multiplier against the number of requested bytes to wait after
|
|
||||||
* calling write.
|
|
||||||
*/
|
|
||||||
uint32_t write_timeout_multiplier;
|
|
||||||
|
|
||||||
explicit Timeout (uint32_t inter_byte_timeout_=0,
|
|
||||||
uint32_t read_timeout_constant_=0,
|
|
||||||
uint32_t read_timeout_multiplier_=0,
|
|
||||||
uint32_t write_timeout_constant_=0,
|
|
||||||
uint32_t write_timeout_multiplier_=0)
|
|
||||||
: inter_byte_timeout(inter_byte_timeout_),
|
|
||||||
read_timeout_constant(read_timeout_constant_),
|
|
||||||
read_timeout_multiplier(read_timeout_multiplier_),
|
|
||||||
write_timeout_constant(write_timeout_constant_),
|
|
||||||
write_timeout_multiplier(write_timeout_multiplier_)
|
|
||||||
{}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Class that provides a portable serial port interface.
|
|
||||||
*/
|
|
||||||
class Serial {
|
|
||||||
public:
|
|
||||||
/*!
|
|
||||||
* Creates a Serial object and opens the port if a port is specified,
|
|
||||||
* otherwise it remains closed until serial::Serial::open is called.
|
|
||||||
*
|
|
||||||
* \param port A std::string containing the address of the serial port,
|
|
||||||
* which would be something like 'COM1' on Windows and '/dev/ttyS0'
|
|
||||||
* on Linux.
|
|
||||||
*
|
|
||||||
* \param baudrate An unsigned 32-bit integer that represents the baudrate
|
|
||||||
*
|
|
||||||
* \param timeout A serial::Timeout struct that defines the timeout
|
|
||||||
* conditions for the serial port. \see serial::Timeout
|
|
||||||
*
|
|
||||||
* \param bytesize Size of each byte in the serial transmission of data,
|
|
||||||
* default is eightbits, possible values are: fivebits, sixbits, sevenbits,
|
|
||||||
* eightbits
|
|
||||||
*
|
|
||||||
* \param parity Method of parity, default is parity_none, possible values
|
|
||||||
* are: parity_none, parity_odd, parity_even
|
|
||||||
*
|
|
||||||
* \param stopbits Number of stop bits used, default is stopbits_one,
|
|
||||||
* possible values are: stopbits_one, stopbits_one_point_five, stopbits_two
|
|
||||||
*
|
|
||||||
* \param flowcontrol Type of flowcontrol used, default is
|
|
||||||
* flowcontrol_none, possible values are: flowcontrol_none,
|
|
||||||
* flowcontrol_software, flowcontrol_hardware
|
|
||||||
*
|
|
||||||
* \throw serial::PortNotOpenedException
|
|
||||||
* \throw serial::IOException
|
|
||||||
* \throw std::invalid_argument
|
|
||||||
*/
|
|
||||||
Serial (const std::string &port = "",
|
|
||||||
uint32_t baudrate = 9600,
|
|
||||||
Timeout timeout = Timeout(),
|
|
||||||
bytesize_t bytesize = eightbits,
|
|
||||||
parity_t parity = parity_none,
|
|
||||||
stopbits_t stopbits = stopbits_one,
|
|
||||||
flowcontrol_t flowcontrol = flowcontrol_none);
|
|
||||||
|
|
||||||
/*! Destructor */
|
|
||||||
virtual ~Serial ();
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Opens the serial port as long as the port is set and the port isn't
|
|
||||||
* already open.
|
|
||||||
*
|
|
||||||
* If the port is provided to the constructor then an explicit call to open
|
|
||||||
* is not needed.
|
|
||||||
*
|
|
||||||
* \see Serial::Serial
|
|
||||||
*
|
|
||||||
* \throw std::invalid_argument
|
|
||||||
* \throw serial::SerialException
|
|
||||||
* \throw serial::IOException
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
open ();
|
|
||||||
|
|
||||||
/*! Gets the open status of the serial port.
|
|
||||||
*
|
|
||||||
* \return Returns true if the port is open, false otherwise.
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
isOpen () const;
|
|
||||||
|
|
||||||
/*! Closes the serial port. */
|
|
||||||
void
|
|
||||||
close ();
|
|
||||||
|
|
||||||
/*! Return the number of characters in the buffer. */
|
|
||||||
size_t
|
|
||||||
available ();
|
|
||||||
|
|
||||||
/*! Block until there is serial data to read or read_timeout_constant
|
|
||||||
* number of milliseconds have elapsed. The return value is true when
|
|
||||||
* the function exits with the port in a readable state, false otherwise
|
|
||||||
* (due to timeout or select interruption). */
|
|
||||||
bool
|
|
||||||
waitReadable ();
|
|
||||||
|
|
||||||
/*! Block for a period of time corresponding to the transmission time of
|
|
||||||
* count characters at present serial settings. This may be used in con-
|
|
||||||
* junction with waitReadable to read larger blocks of data from the
|
|
||||||
* port. */
|
|
||||||
void
|
|
||||||
waitByteTimes (size_t count);
|
|
||||||
|
|
||||||
/*! Read a given amount of bytes from the serial port into a given buffer.
|
|
||||||
*
|
|
||||||
* The read function will return in one of three cases:
|
|
||||||
* * The number of requested bytes was read.
|
|
||||||
* * In this case the number of bytes requested will match the size_t
|
|
||||||
* returned by read.
|
|
||||||
* * A timeout occurred, in this case the number of bytes read will not
|
|
||||||
* match the amount requested, but no exception will be thrown. One of
|
|
||||||
* two possible timeouts occurred:
|
|
||||||
* * The inter byte timeout expired, this means that number of
|
|
||||||
* milliseconds elapsed between receiving bytes from the serial port
|
|
||||||
* exceeded the inter byte timeout.
|
|
||||||
* * The total timeout expired, which is calculated by multiplying the
|
|
||||||
* read timeout multiplier by the number of requested bytes and then
|
|
||||||
* added to the read timeout constant. If that total number of
|
|
||||||
* milliseconds elapses after the initial call to read a timeout will
|
|
||||||
* occur.
|
|
||||||
* * An exception occurred, in this case an actual exception will be thrown.
|
|
||||||
*
|
|
||||||
* \param buffer An uint8_t array of at least the requested size.
|
|
||||||
* \param size A size_t defining how many bytes to be read.
|
|
||||||
*
|
|
||||||
* \return A size_t representing the number of bytes read as a result of the
|
|
||||||
* call to read.
|
|
||||||
*
|
|
||||||
* \throw serial::PortNotOpenedException
|
|
||||||
* \throw serial::SerialException
|
|
||||||
*/
|
|
||||||
size_t
|
|
||||||
read (uint8_t *buffer, size_t size);
|
|
||||||
|
|
||||||
/*! Read a given amount of bytes from the serial port into a give buffer.
|
|
||||||
*
|
|
||||||
* \param buffer A reference to a std::vector of uint8_t.
|
|
||||||
* \param size A size_t defining how many bytes to be read.
|
|
||||||
*
|
|
||||||
* \return A size_t representing the number of bytes read as a result of the
|
|
||||||
* call to read.
|
|
||||||
*
|
|
||||||
* \throw serial::PortNotOpenedException
|
|
||||||
* \throw serial::SerialException
|
|
||||||
*/
|
|
||||||
size_t
|
|
||||||
read (std::vector<uint8_t> &buffer, size_t size = 1);
|
|
||||||
|
|
||||||
/*! Read a given amount of bytes from the serial port into a give buffer.
|
|
||||||
*
|
|
||||||
* \param buffer A reference to a std::string.
|
|
||||||
* \param size A size_t defining how many bytes to be read.
|
|
||||||
*
|
|
||||||
* \return A size_t representing the number of bytes read as a result of the
|
|
||||||
* call to read.
|
|
||||||
*
|
|
||||||
* \throw serial::PortNotOpenedException
|
|
||||||
* \throw serial::SerialException
|
|
||||||
*/
|
|
||||||
size_t
|
|
||||||
read (std::string &buffer, size_t size = 1);
|
|
||||||
|
|
||||||
/*! Read a given amount of bytes from the serial port and return a string
|
|
||||||
* containing the data.
|
|
||||||
*
|
|
||||||
* \param size A size_t defining how many bytes to be read.
|
|
||||||
*
|
|
||||||
* \return A std::string containing the data read from the port.
|
|
||||||
*
|
|
||||||
* \throw serial::PortNotOpenedException
|
|
||||||
* \throw serial::SerialException
|
|
||||||
*/
|
|
||||||
std::string
|
|
||||||
read (size_t size = 1);
|
|
||||||
|
|
||||||
/*! Reads in a line or until a given delimiter has been processed.
|
|
||||||
*
|
|
||||||
* Reads from the serial port until a single line has been read.
|
|
||||||
*
|
|
||||||
* \param buffer A std::string reference used to store the data.
|
|
||||||
* \param size A maximum length of a line, defaults to 65536 (2^16)
|
|
||||||
* \param eol A string to match against for the EOL.
|
|
||||||
*
|
|
||||||
* \return A size_t representing the number of bytes read.
|
|
||||||
*
|
|
||||||
* \throw serial::PortNotOpenedException
|
|
||||||
* \throw serial::SerialException
|
|
||||||
*/
|
|
||||||
size_t
|
|
||||||
readline (std::string &buffer, size_t size = 65536, std::string eol = "\n");
|
|
||||||
|
|
||||||
/*! Reads in a line or until a given delimiter has been processed.
|
|
||||||
*
|
|
||||||
* Reads from the serial port until a single line has been read.
|
|
||||||
*
|
|
||||||
* \param size A maximum length of a line, defaults to 65536 (2^16)
|
|
||||||
* \param eol A string to match against for the EOL.
|
|
||||||
*
|
|
||||||
* \return A std::string containing the line.
|
|
||||||
*
|
|
||||||
* \throw serial::PortNotOpenedException
|
|
||||||
* \throw serial::SerialException
|
|
||||||
*/
|
|
||||||
std::string
|
|
||||||
readline (size_t size = 65536, std::string eol = "\n");
|
|
||||||
|
|
||||||
/*! Reads in multiple lines until the serial port times out.
|
|
||||||
*
|
|
||||||
* This requires a timeout > 0 before it can be run. It will read until a
|
|
||||||
* timeout occurs and return a list of strings.
|
|
||||||
*
|
|
||||||
* \param size A maximum length of combined lines, defaults to 65536 (2^16)
|
|
||||||
*
|
|
||||||
* \param eol A string to match against for the EOL.
|
|
||||||
*
|
|
||||||
* \return A vector<string> containing the lines.
|
|
||||||
*
|
|
||||||
* \throw serial::PortNotOpenedException
|
|
||||||
* \throw serial::SerialException
|
|
||||||
*/
|
|
||||||
std::vector<std::string>
|
|
||||||
readlines (size_t size = 65536, std::string eol = "\n");
|
|
||||||
|
|
||||||
/*! Write a string to the serial port.
|
|
||||||
*
|
|
||||||
* \param data A const reference containing the data to be written
|
|
||||||
* to the serial port.
|
|
||||||
*
|
|
||||||
* \param size A size_t that indicates how many bytes should be written from
|
|
||||||
* the given data buffer.
|
|
||||||
*
|
|
||||||
* \return A size_t representing the number of bytes actually written to
|
|
||||||
* the serial port.
|
|
||||||
*
|
|
||||||
* \throw serial::PortNotOpenedException
|
|
||||||
* \throw serial::SerialException
|
|
||||||
* \throw serial::IOException
|
|
||||||
*/
|
|
||||||
size_t
|
|
||||||
write (const uint8_t *data, size_t size);
|
|
||||||
|
|
||||||
/*! Write a string to the serial port.
|
|
||||||
*
|
|
||||||
* \param data A const reference containing the data to be written
|
|
||||||
* to the serial port.
|
|
||||||
*
|
|
||||||
* \return A size_t representing the number of bytes actually written to
|
|
||||||
* the serial port.
|
|
||||||
*
|
|
||||||
* \throw serial::PortNotOpenedException
|
|
||||||
* \throw serial::SerialException
|
|
||||||
* \throw serial::IOException
|
|
||||||
*/
|
|
||||||
size_t
|
|
||||||
write (const std::vector<uint8_t> &data);
|
|
||||||
|
|
||||||
/*! Write a string to the serial port.
|
|
||||||
*
|
|
||||||
* \param data A const reference containing the data to be written
|
|
||||||
* to the serial port.
|
|
||||||
*
|
|
||||||
* \return A size_t representing the number of bytes actually written to
|
|
||||||
* the serial port.
|
|
||||||
*
|
|
||||||
* \throw serial::PortNotOpenedException
|
|
||||||
* \throw serial::SerialException
|
|
||||||
* \throw serial::IOException
|
|
||||||
*/
|
|
||||||
size_t
|
|
||||||
write (const std::string &data);
|
|
||||||
|
|
||||||
/*! Sets the serial port identifier.
|
|
||||||
*
|
|
||||||
* \param port A const std::string reference containing the address of the
|
|
||||||
* serial port, which would be something like 'COM1' on Windows and
|
|
||||||
* '/dev/ttyS0' on Linux.
|
|
||||||
*
|
|
||||||
* \throw std::invalid_argument
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
setPort (const std::string &port);
|
|
||||||
|
|
||||||
/*! Gets the serial port identifier.
|
|
||||||
*
|
|
||||||
* \see Serial::setPort
|
|
||||||
*
|
|
||||||
* \throw std::invalid_argument
|
|
||||||
*/
|
|
||||||
std::string
|
|
||||||
getPort () const;
|
|
||||||
|
|
||||||
/*! Sets the timeout for reads and writes using the Timeout struct.
|
|
||||||
*
|
|
||||||
* There are two timeout conditions described here:
|
|
||||||
* * The inter byte timeout:
|
|
||||||
* * The inter_byte_timeout component of serial::Timeout defines the
|
|
||||||
* maximum amount of time, in milliseconds, between receiving bytes on
|
|
||||||
* the serial port that can pass before a timeout occurs. Setting this
|
|
||||||
* to zero will prevent inter byte timeouts from occurring.
|
|
||||||
* * Total time timeout:
|
|
||||||
* * The constant and multiplier component of this timeout condition,
|
|
||||||
* for both read and write, are defined in serial::Timeout. This
|
|
||||||
* timeout occurs if the total time since the read or write call was
|
|
||||||
* made exceeds the specified time in milliseconds.
|
|
||||||
* * The limit is defined by multiplying the multiplier component by the
|
|
||||||
* number of requested bytes and adding that product to the constant
|
|
||||||
* component. In this way if you want a read call, for example, to
|
|
||||||
* timeout after exactly one second regardless of the number of bytes
|
|
||||||
* you asked for then set the read_timeout_constant component of
|
|
||||||
* serial::Timeout to 1000 and the read_timeout_multiplier to zero.
|
|
||||||
* This timeout condition can be used in conjunction with the inter
|
|
||||||
* byte timeout condition with out any problems, timeout will simply
|
|
||||||
* occur when one of the two timeout conditions is met. This allows
|
|
||||||
* users to have maximum control over the trade-off between
|
|
||||||
* responsiveness and efficiency.
|
|
||||||
*
|
|
||||||
* Read and write functions will return in one of three cases. When the
|
|
||||||
* reading or writing is complete, when a timeout occurs, or when an
|
|
||||||
* exception occurs.
|
|
||||||
*
|
|
||||||
* A timeout of 0 enables non-blocking mode.
|
|
||||||
*
|
|
||||||
* \param timeout A serial::Timeout struct containing the inter byte
|
|
||||||
* timeout, and the read and write timeout constants and multipliers.
|
|
||||||
*
|
|
||||||
* \see serial::Timeout
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
setTimeout (Timeout &timeout);
|
|
||||||
|
|
||||||
/*! Sets the timeout for reads and writes. */
|
|
||||||
void
|
|
||||||
setTimeout (uint32_t inter_byte_timeout, uint32_t read_timeout_constant,
|
|
||||||
uint32_t read_timeout_multiplier, uint32_t write_timeout_constant,
|
|
||||||
uint32_t write_timeout_multiplier)
|
|
||||||
{
|
|
||||||
Timeout timeout(inter_byte_timeout, read_timeout_constant,
|
|
||||||
read_timeout_multiplier, write_timeout_constant,
|
|
||||||
write_timeout_multiplier);
|
|
||||||
return setTimeout(timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*! Gets the timeout for reads in seconds.
|
|
||||||
*
|
|
||||||
* \return A Timeout struct containing the inter_byte_timeout, and read
|
|
||||||
* and write timeout constants and multipliers.
|
|
||||||
*
|
|
||||||
* \see Serial::setTimeout
|
|
||||||
*/
|
|
||||||
Timeout
|
|
||||||
getTimeout () const;
|
|
||||||
|
|
||||||
/*! Sets the baudrate for the serial port.
|
|
||||||
*
|
|
||||||
* Possible baudrates depends on the system but some safe baudrates include:
|
|
||||||
* 110, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 56000,
|
|
||||||
* 57600, 115200
|
|
||||||
* Some other baudrates that are supported by some comports:
|
|
||||||
* 128000, 153600, 230400, 256000, 460800, 500000, 921600
|
|
||||||
*
|
|
||||||
* \param baudrate An integer that sets the baud rate for the serial port.
|
|
||||||
*
|
|
||||||
* \throw std::invalid_argument
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
setBaudrate (uint32_t baudrate);
|
|
||||||
|
|
||||||
/*! Gets the baudrate for the serial port.
|
|
||||||
*
|
|
||||||
* \return An integer that sets the baud rate for the serial port.
|
|
||||||
*
|
|
||||||
* \see Serial::setBaudrate
|
|
||||||
*
|
|
||||||
* \throw std::invalid_argument
|
|
||||||
*/
|
|
||||||
uint32_t
|
|
||||||
getBaudrate () const;
|
|
||||||
|
|
||||||
/*! Sets the bytesize for the serial port.
|
|
||||||
*
|
|
||||||
* \param bytesize Size of each byte in the serial transmission of data,
|
|
||||||
* default is eightbits, possible values are: fivebits, sixbits, sevenbits,
|
|
||||||
* eightbits
|
|
||||||
*
|
|
||||||
* \throw std::invalid_argument
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
setBytesize (bytesize_t bytesize);
|
|
||||||
|
|
||||||
/*! Gets the bytesize for the serial port.
|
|
||||||
*
|
|
||||||
* \see Serial::setBytesize
|
|
||||||
*
|
|
||||||
* \throw std::invalid_argument
|
|
||||||
*/
|
|
||||||
bytesize_t
|
|
||||||
getBytesize () const;
|
|
||||||
|
|
||||||
/*! Sets the parity for the serial port.
|
|
||||||
*
|
|
||||||
* \param parity Method of parity, default is parity_none, possible values
|
|
||||||
* are: parity_none, parity_odd, parity_even
|
|
||||||
*
|
|
||||||
* \throw std::invalid_argument
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
setParity (parity_t parity);
|
|
||||||
|
|
||||||
/*! Gets the parity for the serial port.
|
|
||||||
*
|
|
||||||
* \see Serial::setParity
|
|
||||||
*
|
|
||||||
* \throw std::invalid_argument
|
|
||||||
*/
|
|
||||||
parity_t
|
|
||||||
getParity () const;
|
|
||||||
|
|
||||||
/*! Sets the stopbits for the serial port.
|
|
||||||
*
|
|
||||||
* \param stopbits Number of stop bits used, default is stopbits_one,
|
|
||||||
* possible values are: stopbits_one, stopbits_one_point_five, stopbits_two
|
|
||||||
*
|
|
||||||
* \throw std::invalid_argument
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
setStopbits (stopbits_t stopbits);
|
|
||||||
|
|
||||||
/*! Gets the stopbits for the serial port.
|
|
||||||
*
|
|
||||||
* \see Serial::setStopbits
|
|
||||||
*
|
|
||||||
* \throw std::invalid_argument
|
|
||||||
*/
|
|
||||||
stopbits_t
|
|
||||||
getStopbits () const;
|
|
||||||
|
|
||||||
/*! Sets the flow control for the serial port.
|
|
||||||
*
|
|
||||||
* \param flowcontrol Type of flowcontrol used, default is flowcontrol_none,
|
|
||||||
* possible values are: flowcontrol_none, flowcontrol_software,
|
|
||||||
* flowcontrol_hardware
|
|
||||||
*
|
|
||||||
* \throw std::invalid_argument
|
|
||||||
*/
|
|
||||||
void
|
|
||||||
setFlowcontrol (flowcontrol_t flowcontrol);
|
|
||||||
|
|
||||||
/*! Gets the flow control for the serial port.
|
|
||||||
*
|
|
||||||
* \see Serial::setFlowcontrol
|
|
||||||
*
|
|
||||||
* \throw std::invalid_argument
|
|
||||||
*/
|
|
||||||
flowcontrol_t
|
|
||||||
getFlowcontrol () const;
|
|
||||||
|
|
||||||
/*! Flush the input and output buffers */
|
|
||||||
void
|
|
||||||
flush ();
|
|
||||||
|
|
||||||
/*! Flush only the input buffer */
|
|
||||||
void
|
|
||||||
flushInput ();
|
|
||||||
|
|
||||||
/*! Flush only the output buffer */
|
|
||||||
void
|
|
||||||
flushOutput ();
|
|
||||||
|
|
||||||
/*! Sends the RS-232 break signal. See tcsendbreak(3). */
|
|
||||||
void
|
|
||||||
sendBreak (int duration);
|
|
||||||
|
|
||||||
/*! Set the break condition to a given level. Defaults to true. */
|
|
||||||
void
|
|
||||||
setBreak (bool level = true);
|
|
||||||
|
|
||||||
/*! Set the RTS handshaking line to the given level. Defaults to true. */
|
|
||||||
void
|
|
||||||
setRTS (bool level = true);
|
|
||||||
|
|
||||||
/*! Set the DTR handshaking line to the given level. Defaults to true. */
|
|
||||||
void
|
|
||||||
setDTR (bool level = true);
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Blocks until CTS, DSR, RI, CD changes or something interrupts it.
|
|
||||||
*
|
|
||||||
* Can throw an exception if an error occurs while waiting.
|
|
||||||
* You can check the status of CTS, DSR, RI, and CD once this returns.
|
|
||||||
* Uses TIOCMIWAIT via ioctl if available (mostly only on Linux) with a
|
|
||||||
* resolution of less than +-1ms and as good as +-0.2ms. Otherwise a
|
|
||||||
* polling method is used which can give +-2ms.
|
|
||||||
*
|
|
||||||
* \return Returns true if one of the lines changed, false if something else
|
|
||||||
* occurred.
|
|
||||||
*
|
|
||||||
* \throw SerialException
|
|
||||||
*/
|
|
||||||
bool
|
|
||||||
waitForChange ();
|
|
||||||
|
|
||||||
/*! Returns the current status of the CTS line. */
|
|
||||||
bool
|
|
||||||
getCTS ();
|
|
||||||
|
|
||||||
/*! Returns the current status of the DSR line. */
|
|
||||||
bool
|
|
||||||
getDSR ();
|
|
||||||
|
|
||||||
/*! Returns the current status of the RI line. */
|
|
||||||
bool
|
|
||||||
getRI ();
|
|
||||||
|
|
||||||
/*! Returns the current status of the CD line. */
|
|
||||||
bool
|
|
||||||
getCD ();
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Disable copy constructors
|
|
||||||
Serial(const Serial&);
|
|
||||||
Serial& operator=(const Serial&);
|
|
||||||
|
|
||||||
// Pimpl idiom, d_pointer
|
|
||||||
class SerialImpl;
|
|
||||||
SerialImpl *pimpl_;
|
|
||||||
|
|
||||||
// Scoped Lock Classes
|
|
||||||
class ScopedReadLock;
|
|
||||||
class ScopedWriteLock;
|
|
||||||
|
|
||||||
// Read common function
|
|
||||||
size_t
|
|
||||||
read_ (uint8_t *buffer, size_t size);
|
|
||||||
// Write common function
|
|
||||||
size_t
|
|
||||||
write_ (const uint8_t *data, size_t length);
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
class SerialException : public std::exception
|
|
||||||
{
|
|
||||||
// Disable copy constructors
|
|
||||||
SerialException& operator=(const SerialException&);
|
|
||||||
std::string e_what_;
|
|
||||||
public:
|
|
||||||
SerialException (const char *description) {
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "SerialException " << description << " failed.";
|
|
||||||
e_what_ = ss.str();
|
|
||||||
}
|
|
||||||
SerialException (const SerialException& other) : e_what_(other.e_what_) {}
|
|
||||||
virtual ~SerialException() throw() {}
|
|
||||||
virtual const char* what () const throw () {
|
|
||||||
return e_what_.c_str();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class IOException : public std::exception
|
|
||||||
{
|
|
||||||
// Disable copy constructors
|
|
||||||
IOException& operator=(const IOException&);
|
|
||||||
std::string file_;
|
|
||||||
int line_;
|
|
||||||
std::string e_what_;
|
|
||||||
int errno_;
|
|
||||||
public:
|
|
||||||
explicit IOException (std::string file, int line, int errnum)
|
|
||||||
: file_(file), line_(line), errno_(errnum) {
|
|
||||||
std::stringstream ss;
|
|
||||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
|
||||||
char error_str [1024];
|
|
||||||
strerror_s(error_str, 1024, errnum);
|
|
||||||
#else
|
|
||||||
char * error_str = strerror(errnum);
|
|
||||||
#endif
|
|
||||||
ss << "IO Exception (" << errno_ << "): " << error_str;
|
|
||||||
ss << ", file " << file_ << ", line " << line_ << ".";
|
|
||||||
e_what_ = ss.str();
|
|
||||||
}
|
|
||||||
explicit IOException (std::string file, int line, const char * description)
|
|
||||||
: file_(file), line_(line), errno_(0) {
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "IO Exception: " << description;
|
|
||||||
ss << ", file " << file_ << ", line " << line_ << ".";
|
|
||||||
e_what_ = ss.str();
|
|
||||||
}
|
|
||||||
virtual ~IOException() throw() {}
|
|
||||||
IOException (const IOException& other) : line_(other.line_), e_what_(other.e_what_), errno_(other.errno_) {}
|
|
||||||
|
|
||||||
int getErrorNumber () const { return errno_; }
|
|
||||||
|
|
||||||
virtual const char* what () const throw () {
|
|
||||||
return e_what_.c_str();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
class PortNotOpenedException : public std::exception
|
|
||||||
{
|
|
||||||
// Disable copy constructors
|
|
||||||
const PortNotOpenedException& operator=(PortNotOpenedException);
|
|
||||||
std::string e_what_;
|
|
||||||
public:
|
|
||||||
PortNotOpenedException (const char * description) {
|
|
||||||
std::stringstream ss;
|
|
||||||
ss << "PortNotOpenedException " << description << " failed.";
|
|
||||||
e_what_ = ss.str();
|
|
||||||
}
|
|
||||||
PortNotOpenedException (const PortNotOpenedException& other) : e_what_(other.e_what_) {}
|
|
||||||
virtual ~PortNotOpenedException() throw() {}
|
|
||||||
virtual const char* what () const throw () {
|
|
||||||
return e_what_.c_str();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
|
||||||
* Structure that describes a serial device.
|
|
||||||
*/
|
|
||||||
struct PortInfo {
|
|
||||||
|
|
||||||
/*! Address of the serial port (this can be passed to the constructor of Serial). */
|
|
||||||
std::string port;
|
|
||||||
|
|
||||||
/*! Human readable description of serial device if available. */
|
|
||||||
std::string description;
|
|
||||||
|
|
||||||
/*! Hardware ID (e.g. VID:PID of USB serial devices) or "n/a" if not available. */
|
|
||||||
std::string hardware_id;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Lists the serial ports available on the system
|
|
||||||
*
|
|
||||||
* Returns a vector of available serial ports, each represented
|
|
||||||
* by a serial::PortInfo data structure:
|
|
||||||
*
|
|
||||||
* \return vector of serial::PortInfo.
|
|
||||||
*/
|
|
||||||
std::vector<PortInfo>
|
|
||||||
list_ports();
|
|
||||||
|
|
||||||
} // namespace serial
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
// This header is from the v8 google project:
|
|
||||||
// http://code.google.com/p/v8/source/browse/trunk/include/v8stdint.h
|
|
||||||
|
|
||||||
// Copyright 2012 the V8 project authors. All rights reserved.
|
|
||||||
// Redistribution and use in source and binary forms, with or without
|
|
||||||
// modification, are permitted provided that the following conditions are
|
|
||||||
// met:
|
|
||||||
//
|
|
||||||
// * Redistributions of source code must retain the above copyright
|
|
||||||
// notice, this list of conditions and the following disclaimer.
|
|
||||||
// * Redistributions in binary form must reproduce the above
|
|
||||||
// copyright notice, this list of conditions and the following
|
|
||||||
// disclaimer in the documentation and/or other materials provided
|
|
||||||
// with the distribution.
|
|
||||||
// * Neither the name of Google Inc. nor the names of its
|
|
||||||
// contributors may be used to endorse or promote products derived
|
|
||||||
// from this software without specific prior written permission.
|
|
||||||
//
|
|
||||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
// Load definitions of standard types.
|
|
||||||
|
|
||||||
#ifndef V8STDINT_H_
|
|
||||||
#define V8STDINT_H_
|
|
||||||
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
|
|
||||||
#if defined(_WIN32) && !defined(__MINGW32__)
|
|
||||||
|
|
||||||
typedef signed char int8_t;
|
|
||||||
typedef unsigned char uint8_t;
|
|
||||||
typedef short int16_t; // NOLINT
|
|
||||||
typedef unsigned short uint16_t; // NOLINT
|
|
||||||
typedef int int32_t;
|
|
||||||
typedef unsigned int uint32_t;
|
|
||||||
typedef __int64 int64_t;
|
|
||||||
typedef unsigned __int64 uint64_t;
|
|
||||||
// intptr_t and friends are defined in crtdefs.h through stdio.h.
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // V8STDINT_H_
|
|
||||||
@@ -1,336 +0,0 @@
|
|||||||
#if defined(__linux__)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright (c) 2014 Craig Lilley <cralilley@gmail.com>
|
|
||||||
* This software is made available under the terms of the MIT licence.
|
|
||||||
* A copy of the licence can be obtained from:
|
|
||||||
* http://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
#include <sstream>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <iostream>
|
|
||||||
#include <fstream>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdarg>
|
|
||||||
#include <cstdlib>
|
|
||||||
|
|
||||||
#include <glob.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
|
|
||||||
#include "serial/serial.h"
|
|
||||||
|
|
||||||
using serial::PortInfo;
|
|
||||||
using std::istringstream;
|
|
||||||
using std::ifstream;
|
|
||||||
using std::getline;
|
|
||||||
using std::vector;
|
|
||||||
using std::string;
|
|
||||||
using std::cout;
|
|
||||||
using std::endl;
|
|
||||||
|
|
||||||
static vector<string> glob(const vector<string>& patterns);
|
|
||||||
static string basename(const string& path);
|
|
||||||
static string dirname(const string& path);
|
|
||||||
static bool path_exists(const string& path);
|
|
||||||
static string realpath(const string& path);
|
|
||||||
static string usb_sysfs_friendly_name(const string& sys_usb_path);
|
|
||||||
static vector<string> get_sysfs_info(const string& device_path);
|
|
||||||
static string read_line(const string& file);
|
|
||||||
static string usb_sysfs_hw_string(const string& sysfs_path);
|
|
||||||
static string format(const char* format, ...);
|
|
||||||
|
|
||||||
vector<string>
|
|
||||||
glob(const vector<string>& patterns)
|
|
||||||
{
|
|
||||||
vector<string> paths_found;
|
|
||||||
|
|
||||||
if(patterns.size() == 0)
|
|
||||||
return paths_found;
|
|
||||||
|
|
||||||
glob_t glob_results;
|
|
||||||
|
|
||||||
int glob_retval = glob(patterns[0].c_str(), 0, NULL, &glob_results);
|
|
||||||
|
|
||||||
vector<string>::const_iterator iter = patterns.begin();
|
|
||||||
|
|
||||||
while(++iter != patterns.end())
|
|
||||||
{
|
|
||||||
glob_retval = glob(iter->c_str(), GLOB_APPEND, NULL, &glob_results);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(int path_index = 0; path_index < glob_results.gl_pathc; path_index++)
|
|
||||||
{
|
|
||||||
paths_found.push_back(glob_results.gl_pathv[path_index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
globfree(&glob_results);
|
|
||||||
|
|
||||||
return paths_found;
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
basename(const string& path)
|
|
||||||
{
|
|
||||||
size_t pos = path.rfind("/");
|
|
||||||
|
|
||||||
if(pos == std::string::npos)
|
|
||||||
return path;
|
|
||||||
|
|
||||||
return string(path, pos+1, string::npos);
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
dirname(const string& path)
|
|
||||||
{
|
|
||||||
size_t pos = path.rfind("/");
|
|
||||||
|
|
||||||
if(pos == std::string::npos)
|
|
||||||
return path;
|
|
||||||
else if(pos == 0)
|
|
||||||
return "/";
|
|
||||||
|
|
||||||
return string(path, 0, pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
path_exists(const string& path)
|
|
||||||
{
|
|
||||||
struct stat sb;
|
|
||||||
|
|
||||||
if( stat(path.c_str(), &sb ) == 0 )
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
realpath(const string& path)
|
|
||||||
{
|
|
||||||
char* real_path = realpath(path.c_str(), NULL);
|
|
||||||
|
|
||||||
string result;
|
|
||||||
|
|
||||||
if(real_path != NULL)
|
|
||||||
{
|
|
||||||
result = real_path;
|
|
||||||
|
|
||||||
free(real_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
usb_sysfs_friendly_name(const string& sys_usb_path)
|
|
||||||
{
|
|
||||||
unsigned int device_number = 0;
|
|
||||||
|
|
||||||
istringstream( read_line(sys_usb_path + "/devnum") ) >> device_number;
|
|
||||||
|
|
||||||
string manufacturer = read_line( sys_usb_path + "/manufacturer" );
|
|
||||||
|
|
||||||
string product = read_line( sys_usb_path + "/product" );
|
|
||||||
|
|
||||||
string serial = read_line( sys_usb_path + "/serial" );
|
|
||||||
|
|
||||||
if( manufacturer.empty() && product.empty() && serial.empty() )
|
|
||||||
return "";
|
|
||||||
|
|
||||||
return format("%s %s %s", manufacturer.c_str(), product.c_str(), serial.c_str() );
|
|
||||||
}
|
|
||||||
|
|
||||||
vector<string>
|
|
||||||
get_sysfs_info(const string& device_path)
|
|
||||||
{
|
|
||||||
string device_name = basename( device_path );
|
|
||||||
|
|
||||||
string friendly_name;
|
|
||||||
|
|
||||||
string hardware_id;
|
|
||||||
|
|
||||||
string sys_device_path = format( "/sys/class/tty/%s/device", device_name.c_str() );
|
|
||||||
|
|
||||||
if( device_name.compare(0,6,"ttyUSB") == 0 )
|
|
||||||
{
|
|
||||||
sys_device_path = dirname( dirname( realpath( sys_device_path ) ) );
|
|
||||||
|
|
||||||
if( path_exists( sys_device_path ) )
|
|
||||||
{
|
|
||||||
friendly_name = usb_sysfs_friendly_name( sys_device_path );
|
|
||||||
|
|
||||||
hardware_id = usb_sysfs_hw_string( sys_device_path );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if( device_name.compare(0,6,"ttyACM") == 0 )
|
|
||||||
{
|
|
||||||
sys_device_path = dirname( realpath( sys_device_path ) );
|
|
||||||
|
|
||||||
if( path_exists( sys_device_path ) )
|
|
||||||
{
|
|
||||||
friendly_name = usb_sysfs_friendly_name( sys_device_path );
|
|
||||||
|
|
||||||
hardware_id = usb_sysfs_hw_string( sys_device_path );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Try to read ID string of PCI device
|
|
||||||
|
|
||||||
string sys_id_path = sys_device_path + "/id";
|
|
||||||
|
|
||||||
if( path_exists( sys_id_path ) )
|
|
||||||
hardware_id = read_line( sys_id_path );
|
|
||||||
}
|
|
||||||
|
|
||||||
if( friendly_name.empty() )
|
|
||||||
friendly_name = device_name;
|
|
||||||
|
|
||||||
if( hardware_id.empty() )
|
|
||||||
hardware_id = "n/a";
|
|
||||||
|
|
||||||
vector<string> result;
|
|
||||||
result.push_back(friendly_name);
|
|
||||||
result.push_back(hardware_id);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
read_line(const string& file)
|
|
||||||
{
|
|
||||||
ifstream ifs(file.c_str(), ifstream::in);
|
|
||||||
|
|
||||||
string line;
|
|
||||||
|
|
||||||
if(ifs)
|
|
||||||
{
|
|
||||||
getline(ifs, line);
|
|
||||||
}
|
|
||||||
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
format(const char* format, ...)
|
|
||||||
{
|
|
||||||
va_list ap;
|
|
||||||
|
|
||||||
size_t buffer_size_bytes = 256;
|
|
||||||
|
|
||||||
string result;
|
|
||||||
|
|
||||||
char* buffer = (char*)malloc(buffer_size_bytes);
|
|
||||||
|
|
||||||
if( buffer == NULL )
|
|
||||||
return result;
|
|
||||||
|
|
||||||
bool done = false;
|
|
||||||
|
|
||||||
unsigned int loop_count = 0;
|
|
||||||
|
|
||||||
while(!done)
|
|
||||||
{
|
|
||||||
va_start(ap, format);
|
|
||||||
|
|
||||||
int return_value = vsnprintf(buffer, buffer_size_bytes, format, ap);
|
|
||||||
|
|
||||||
if( return_value < 0 )
|
|
||||||
{
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
else if( return_value >= buffer_size_bytes )
|
|
||||||
{
|
|
||||||
// Realloc and try again.
|
|
||||||
|
|
||||||
buffer_size_bytes = return_value + 1;
|
|
||||||
|
|
||||||
char* new_buffer_ptr = (char*)realloc(buffer, buffer_size_bytes);
|
|
||||||
|
|
||||||
if( new_buffer_ptr == NULL )
|
|
||||||
{
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
buffer = new_buffer_ptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = buffer;
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
va_end(ap);
|
|
||||||
|
|
||||||
if( ++loop_count > 5 )
|
|
||||||
done = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
free(buffer);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
usb_sysfs_hw_string(const string& sysfs_path)
|
|
||||||
{
|
|
||||||
string serial_number = read_line( sysfs_path + "/serial" );
|
|
||||||
|
|
||||||
if( serial_number.length() > 0 )
|
|
||||||
{
|
|
||||||
serial_number = format( "SNR=%s", serial_number.c_str() );
|
|
||||||
}
|
|
||||||
|
|
||||||
string vid = read_line( sysfs_path + "/idVendor" );
|
|
||||||
|
|
||||||
string pid = read_line( sysfs_path + "/idProduct" );
|
|
||||||
|
|
||||||
return format("USB VID:PID=%s:%s %s", vid.c_str(), pid.c_str(), serial_number.c_str() );
|
|
||||||
}
|
|
||||||
|
|
||||||
vector<PortInfo>
|
|
||||||
serial::list_ports()
|
|
||||||
{
|
|
||||||
vector<PortInfo> results;
|
|
||||||
|
|
||||||
vector<string> search_globs;
|
|
||||||
search_globs.push_back("/dev/ttyACM*");
|
|
||||||
search_globs.push_back("/dev/ttyS*");
|
|
||||||
search_globs.push_back("/dev/ttyUSB*");
|
|
||||||
search_globs.push_back("/dev/tty.*");
|
|
||||||
search_globs.push_back("/dev/cu.*");
|
|
||||||
search_globs.push_back("/dev/rfcomm*");
|
|
||||||
|
|
||||||
vector<string> devices_found = glob( search_globs );
|
|
||||||
|
|
||||||
vector<string>::iterator iter = devices_found.begin();
|
|
||||||
|
|
||||||
while( iter != devices_found.end() )
|
|
||||||
{
|
|
||||||
string device = *iter++;
|
|
||||||
|
|
||||||
vector<string> sysfs_info = get_sysfs_info( device );
|
|
||||||
|
|
||||||
string friendly_name = sysfs_info[0];
|
|
||||||
|
|
||||||
string hardware_id = sysfs_info[1];
|
|
||||||
|
|
||||||
PortInfo device_entry;
|
|
||||||
device_entry.port = device;
|
|
||||||
device_entry.description = friendly_name;
|
|
||||||
device_entry.hardware_id = hardware_id;
|
|
||||||
|
|
||||||
results.push_back( device_entry );
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // defined(__linux__)
|
|
||||||
@@ -1,286 +0,0 @@
|
|||||||
#if defined(__APPLE__)
|
|
||||||
|
|
||||||
#include <sys/param.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
#include <CoreFoundation/CoreFoundation.h>
|
|
||||||
#include <IOKit/IOKitLib.h>
|
|
||||||
#include <IOKit/serial/IOSerialKeys.h>
|
|
||||||
#include <IOKit/IOBSD.h>
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <string>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "serial/serial.h"
|
|
||||||
|
|
||||||
using serial::PortInfo;
|
|
||||||
using std::string;
|
|
||||||
using std::vector;
|
|
||||||
|
|
||||||
#define HARDWARE_ID_STRING_LENGTH 128
|
|
||||||
|
|
||||||
string cfstring_to_string( CFStringRef cfstring );
|
|
||||||
string get_device_path( io_object_t& serial_port );
|
|
||||||
string get_class_name( io_object_t& obj );
|
|
||||||
io_registry_entry_t get_parent_iousb_device( io_object_t& serial_port );
|
|
||||||
string get_string_property( io_object_t& device, const char* property );
|
|
||||||
uint16_t get_int_property( io_object_t& device, const char* property );
|
|
||||||
string rtrim(const string& str);
|
|
||||||
|
|
||||||
string
|
|
||||||
cfstring_to_string( CFStringRef cfstring )
|
|
||||||
{
|
|
||||||
char cstring[MAXPATHLEN];
|
|
||||||
string result;
|
|
||||||
|
|
||||||
if( cfstring )
|
|
||||||
{
|
|
||||||
Boolean success = CFStringGetCString( cfstring,
|
|
||||||
cstring,
|
|
||||||
sizeof(cstring),
|
|
||||||
kCFStringEncodingASCII );
|
|
||||||
|
|
||||||
if( success )
|
|
||||||
result = cstring;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
get_device_path( io_object_t& serial_port )
|
|
||||||
{
|
|
||||||
CFTypeRef callout_path;
|
|
||||||
string device_path;
|
|
||||||
|
|
||||||
callout_path = IORegistryEntryCreateCFProperty( serial_port,
|
|
||||||
CFSTR(kIOCalloutDeviceKey),
|
|
||||||
kCFAllocatorDefault,
|
|
||||||
0 );
|
|
||||||
|
|
||||||
if (callout_path)
|
|
||||||
{
|
|
||||||
if( CFGetTypeID(callout_path) == CFStringGetTypeID() )
|
|
||||||
device_path = cfstring_to_string( static_cast<CFStringRef>(callout_path) );
|
|
||||||
|
|
||||||
CFRelease(callout_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
return device_path;
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
get_class_name( io_object_t& obj )
|
|
||||||
{
|
|
||||||
string result;
|
|
||||||
io_name_t class_name;
|
|
||||||
kern_return_t kern_result;
|
|
||||||
|
|
||||||
kern_result = IOObjectGetClass( obj, class_name );
|
|
||||||
|
|
||||||
if( kern_result == KERN_SUCCESS )
|
|
||||||
result = class_name;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
io_registry_entry_t
|
|
||||||
get_parent_iousb_device( io_object_t& serial_port )
|
|
||||||
{
|
|
||||||
io_object_t device = serial_port;
|
|
||||||
io_registry_entry_t parent = 0;
|
|
||||||
io_registry_entry_t result = 0;
|
|
||||||
kern_return_t kern_result = KERN_FAILURE;
|
|
||||||
string name = get_class_name(device);
|
|
||||||
|
|
||||||
// Walk the IO Registry tree looking for this devices parent IOUSBDevice.
|
|
||||||
while( name != "IOUSBDevice" )
|
|
||||||
{
|
|
||||||
kern_result = IORegistryEntryGetParentEntry( device,
|
|
||||||
kIOServicePlane,
|
|
||||||
&parent );
|
|
||||||
|
|
||||||
if(kern_result != KERN_SUCCESS)
|
|
||||||
{
|
|
||||||
result = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
device = parent;
|
|
||||||
|
|
||||||
name = get_class_name(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(kern_result == KERN_SUCCESS)
|
|
||||||
result = device;
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
get_string_property( io_object_t& device, const char* property )
|
|
||||||
{
|
|
||||||
string property_name;
|
|
||||||
|
|
||||||
if( device )
|
|
||||||
{
|
|
||||||
CFStringRef property_as_cfstring = CFStringCreateWithCString (
|
|
||||||
kCFAllocatorDefault,
|
|
||||||
property,
|
|
||||||
kCFStringEncodingASCII );
|
|
||||||
|
|
||||||
CFTypeRef name_as_cfstring = IORegistryEntryCreateCFProperty(
|
|
||||||
device,
|
|
||||||
property_as_cfstring,
|
|
||||||
kCFAllocatorDefault,
|
|
||||||
0 );
|
|
||||||
|
|
||||||
if( name_as_cfstring )
|
|
||||||
{
|
|
||||||
if( CFGetTypeID(name_as_cfstring) == CFStringGetTypeID() )
|
|
||||||
property_name = cfstring_to_string( static_cast<CFStringRef>(name_as_cfstring) );
|
|
||||||
|
|
||||||
CFRelease(name_as_cfstring);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(property_as_cfstring)
|
|
||||||
CFRelease(property_as_cfstring);
|
|
||||||
}
|
|
||||||
|
|
||||||
return property_name;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t
|
|
||||||
get_int_property( io_object_t& device, const char* property )
|
|
||||||
{
|
|
||||||
uint16_t result = 0;
|
|
||||||
|
|
||||||
if( device )
|
|
||||||
{
|
|
||||||
CFStringRef property_as_cfstring = CFStringCreateWithCString (
|
|
||||||
kCFAllocatorDefault,
|
|
||||||
property,
|
|
||||||
kCFStringEncodingASCII );
|
|
||||||
|
|
||||||
CFTypeRef number = IORegistryEntryCreateCFProperty( device,
|
|
||||||
property_as_cfstring,
|
|
||||||
kCFAllocatorDefault,
|
|
||||||
0 );
|
|
||||||
|
|
||||||
if(property_as_cfstring)
|
|
||||||
CFRelease(property_as_cfstring);
|
|
||||||
|
|
||||||
if( number )
|
|
||||||
{
|
|
||||||
if( CFGetTypeID(number) == CFNumberGetTypeID() )
|
|
||||||
{
|
|
||||||
bool success = CFNumberGetValue( static_cast<CFNumberRef>(number),
|
|
||||||
kCFNumberSInt16Type,
|
|
||||||
&result );
|
|
||||||
|
|
||||||
if( !success )
|
|
||||||
result = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
CFRelease(number);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
string rtrim(const string& str)
|
|
||||||
{
|
|
||||||
string result = str;
|
|
||||||
|
|
||||||
string whitespace = " \t\f\v\n\r";
|
|
||||||
|
|
||||||
std::size_t found = result.find_last_not_of(whitespace);
|
|
||||||
|
|
||||||
if (found != std::string::npos)
|
|
||||||
result.erase(found+1);
|
|
||||||
else
|
|
||||||
result.clear();
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
vector<PortInfo>
|
|
||||||
serial::list_ports(void)
|
|
||||||
{
|
|
||||||
vector<PortInfo> devices_found;
|
|
||||||
CFMutableDictionaryRef classes_to_match;
|
|
||||||
io_iterator_t serial_port_iterator;
|
|
||||||
io_object_t serial_port;
|
|
||||||
mach_port_t master_port;
|
|
||||||
kern_return_t kern_result;
|
|
||||||
|
|
||||||
kern_result = IOMasterPort(MACH_PORT_NULL, &master_port);
|
|
||||||
|
|
||||||
if(kern_result != KERN_SUCCESS)
|
|
||||||
return devices_found;
|
|
||||||
|
|
||||||
classes_to_match = IOServiceMatching(kIOSerialBSDServiceValue);
|
|
||||||
|
|
||||||
if (classes_to_match == NULL)
|
|
||||||
return devices_found;
|
|
||||||
|
|
||||||
CFDictionarySetValue( classes_to_match,
|
|
||||||
CFSTR(kIOSerialBSDTypeKey),
|
|
||||||
CFSTR(kIOSerialBSDAllTypes) );
|
|
||||||
|
|
||||||
kern_result = IOServiceGetMatchingServices(master_port, classes_to_match, &serial_port_iterator);
|
|
||||||
|
|
||||||
if (KERN_SUCCESS != kern_result)
|
|
||||||
return devices_found;
|
|
||||||
|
|
||||||
while ( (serial_port = IOIteratorNext(serial_port_iterator)) )
|
|
||||||
{
|
|
||||||
string device_path = get_device_path( serial_port );
|
|
||||||
io_registry_entry_t parent = get_parent_iousb_device( serial_port );
|
|
||||||
IOObjectRelease(serial_port);
|
|
||||||
|
|
||||||
if( device_path.empty() )
|
|
||||||
continue;
|
|
||||||
|
|
||||||
PortInfo port_info;
|
|
||||||
port_info.port = device_path;
|
|
||||||
port_info.description = "n/a";
|
|
||||||
port_info.hardware_id = "n/a";
|
|
||||||
|
|
||||||
string device_name = rtrim( get_string_property( parent, "USB Product Name" ) );
|
|
||||||
string vendor_name = rtrim( get_string_property( parent, "USB Vendor Name") );
|
|
||||||
string description = rtrim( vendor_name + " " + device_name );
|
|
||||||
if( !description.empty() )
|
|
||||||
port_info.description = description;
|
|
||||||
|
|
||||||
string serial_number = rtrim(get_string_property( parent, "USB Serial Number" ) );
|
|
||||||
uint16_t vendor_id = get_int_property( parent, "idVendor" );
|
|
||||||
uint16_t product_id = get_int_property( parent, "idProduct" );
|
|
||||||
|
|
||||||
if( vendor_id && product_id )
|
|
||||||
{
|
|
||||||
char cstring[HARDWARE_ID_STRING_LENGTH];
|
|
||||||
|
|
||||||
if(serial_number.empty())
|
|
||||||
serial_number = "None";
|
|
||||||
|
|
||||||
int ret = snprintf( cstring, HARDWARE_ID_STRING_LENGTH, "USB VID:PID=%04x:%04x SNR=%s",
|
|
||||||
vendor_id,
|
|
||||||
product_id,
|
|
||||||
serial_number.c_str() );
|
|
||||||
|
|
||||||
if( (ret >= 0) && (ret < HARDWARE_ID_STRING_LENGTH) )
|
|
||||||
port_info.hardware_id = cstring;
|
|
||||||
}
|
|
||||||
|
|
||||||
devices_found.push_back(port_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
IOObjectRelease(serial_port_iterator);
|
|
||||||
return devices_found;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // defined(__APPLE__)
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
#if defined(_WIN32)
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright (c) 2014 Craig Lilley <cralilley@gmail.com>
|
|
||||||
* This software is made available under the terms of the MIT licence.
|
|
||||||
* A copy of the licence can be obtained from:
|
|
||||||
* http://opensource.org/licenses/MIT
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "serial/serial.h"
|
|
||||||
#include <tchar.h>
|
|
||||||
#include <windows.h>
|
|
||||||
#include <setupapi.h>
|
|
||||||
#include <initguid.h>
|
|
||||||
#include <devguid.h>
|
|
||||||
#include <cstring>
|
|
||||||
|
|
||||||
using serial::PortInfo;
|
|
||||||
using std::vector;
|
|
||||||
using std::string;
|
|
||||||
|
|
||||||
static const DWORD port_name_max_length = 256;
|
|
||||||
static const DWORD friendly_name_max_length = 256;
|
|
||||||
static const DWORD hardware_id_max_length = 256;
|
|
||||||
|
|
||||||
// Convert a wide Unicode string to an UTF8 string
|
|
||||||
std::string utf8_encode(const std::wstring &wstr)
|
|
||||||
{
|
|
||||||
int size_needed = WideCharToMultiByte(CP_UTF8, 0, &wstr[0], (int)wstr.size(), NULL, 0, NULL, NULL);
|
|
||||||
std::string strTo( size_needed, 0 );
|
|
||||||
WideCharToMultiByte (CP_UTF8, 0, &wstr[0], (int)wstr.size(), &strTo[0], size_needed, NULL, NULL);
|
|
||||||
return strTo;
|
|
||||||
}
|
|
||||||
|
|
||||||
vector<PortInfo>
|
|
||||||
serial::list_ports()
|
|
||||||
{
|
|
||||||
vector<PortInfo> devices_found;
|
|
||||||
|
|
||||||
HDEVINFO device_info_set = SetupDiGetClassDevs(
|
|
||||||
(const GUID *) &GUID_DEVCLASS_PORTS,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
DIGCF_PRESENT);
|
|
||||||
|
|
||||||
unsigned int device_info_set_index = 0;
|
|
||||||
SP_DEVINFO_DATA device_info_data;
|
|
||||||
|
|
||||||
device_info_data.cbSize = sizeof(SP_DEVINFO_DATA);
|
|
||||||
|
|
||||||
while(SetupDiEnumDeviceInfo(device_info_set, device_info_set_index, &device_info_data))
|
|
||||||
{
|
|
||||||
device_info_set_index++;
|
|
||||||
|
|
||||||
// Get port name
|
|
||||||
|
|
||||||
HKEY hkey = SetupDiOpenDevRegKey(
|
|
||||||
device_info_set,
|
|
||||||
&device_info_data,
|
|
||||||
DICS_FLAG_GLOBAL,
|
|
||||||
0,
|
|
||||||
DIREG_DEV,
|
|
||||||
KEY_READ);
|
|
||||||
|
|
||||||
TCHAR port_name[port_name_max_length];
|
|
||||||
DWORD port_name_length = port_name_max_length;
|
|
||||||
|
|
||||||
LONG return_code = RegQueryValueEx(
|
|
||||||
hkey,
|
|
||||||
_T("PortName"),
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
(LPBYTE)port_name,
|
|
||||||
&port_name_length);
|
|
||||||
|
|
||||||
RegCloseKey(hkey);
|
|
||||||
|
|
||||||
if(return_code != EXIT_SUCCESS)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if(port_name_length > 0 && port_name_length <= port_name_max_length)
|
|
||||||
port_name[port_name_length-1] = '\0';
|
|
||||||
else
|
|
||||||
port_name[0] = '\0';
|
|
||||||
|
|
||||||
// Ignore parallel ports
|
|
||||||
|
|
||||||
if(_tcsstr(port_name, _T("LPT")) != NULL)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// Get port friendly name
|
|
||||||
|
|
||||||
TCHAR friendly_name[friendly_name_max_length];
|
|
||||||
DWORD friendly_name_actual_length = 0;
|
|
||||||
|
|
||||||
BOOL got_friendly_name = SetupDiGetDeviceRegistryProperty(
|
|
||||||
device_info_set,
|
|
||||||
&device_info_data,
|
|
||||||
SPDRP_FRIENDLYNAME,
|
|
||||||
NULL,
|
|
||||||
(PBYTE)friendly_name,
|
|
||||||
friendly_name_max_length,
|
|
||||||
&friendly_name_actual_length);
|
|
||||||
|
|
||||||
if(got_friendly_name == TRUE && friendly_name_actual_length > 0)
|
|
||||||
friendly_name[friendly_name_actual_length-1] = '\0';
|
|
||||||
else
|
|
||||||
friendly_name[0] = '\0';
|
|
||||||
|
|
||||||
// Get hardware ID
|
|
||||||
|
|
||||||
TCHAR hardware_id[hardware_id_max_length];
|
|
||||||
DWORD hardware_id_actual_length = 0;
|
|
||||||
|
|
||||||
BOOL got_hardware_id = SetupDiGetDeviceRegistryProperty(
|
|
||||||
device_info_set,
|
|
||||||
&device_info_data,
|
|
||||||
SPDRP_HARDWAREID,
|
|
||||||
NULL,
|
|
||||||
(PBYTE)hardware_id,
|
|
||||||
hardware_id_max_length,
|
|
||||||
&hardware_id_actual_length);
|
|
||||||
|
|
||||||
if(got_hardware_id == TRUE && hardware_id_actual_length > 0)
|
|
||||||
hardware_id[hardware_id_actual_length-1] = '\0';
|
|
||||||
else
|
|
||||||
hardware_id[0] = '\0';
|
|
||||||
|
|
||||||
#ifdef UNICODE
|
|
||||||
std::string portName = utf8_encode(port_name);
|
|
||||||
std::string friendlyName = utf8_encode(friendly_name);
|
|
||||||
std::string hardwareId = utf8_encode(hardware_id);
|
|
||||||
#else
|
|
||||||
std::string portName = port_name;
|
|
||||||
std::string friendlyName = friendly_name;
|
|
||||||
std::string hardwareId = hardware_id;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
PortInfo port_entry;
|
|
||||||
port_entry.port = portName;
|
|
||||||
port_entry.description = friendlyName;
|
|
||||||
port_entry.hardware_id = hardwareId;
|
|
||||||
|
|
||||||
devices_found.push_back(port_entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetupDiDestroyDeviceInfoList(device_info_set);
|
|
||||||
|
|
||||||
return devices_found;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // #if defined(_WIN32)
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,646 +0,0 @@
|
|||||||
#if defined(_WIN32)
|
|
||||||
|
|
||||||
/* Copyright 2012 William Woodall and John Harrison */
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include "serial/impl/win.h"
|
|
||||||
|
|
||||||
using std::string;
|
|
||||||
using std::wstring;
|
|
||||||
using std::stringstream;
|
|
||||||
using std::invalid_argument;
|
|
||||||
using serial::Serial;
|
|
||||||
using serial::Timeout;
|
|
||||||
using serial::bytesize_t;
|
|
||||||
using serial::parity_t;
|
|
||||||
using serial::stopbits_t;
|
|
||||||
using serial::flowcontrol_t;
|
|
||||||
using serial::SerialException;
|
|
||||||
using serial::PortNotOpenedException;
|
|
||||||
using serial::IOException;
|
|
||||||
|
|
||||||
inline wstring
|
|
||||||
_prefix_port_if_needed(const wstring &input)
|
|
||||||
{
|
|
||||||
static wstring windows_com_port_prefix = L"\\\\.\\";
|
|
||||||
if (input.compare(0, windows_com_port_prefix.size(), windows_com_port_prefix) != 0)
|
|
||||||
{
|
|
||||||
return windows_com_port_prefix + input;
|
|
||||||
}
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial::SerialImpl::SerialImpl (const string &port, unsigned long baudrate,
|
|
||||||
bytesize_t bytesize,
|
|
||||||
parity_t parity, stopbits_t stopbits,
|
|
||||||
flowcontrol_t flowcontrol)
|
|
||||||
: port_ (port.begin(), port.end()), fd_ (INVALID_HANDLE_VALUE), is_open_ (false),
|
|
||||||
baudrate_ (baudrate), parity_ (parity),
|
|
||||||
bytesize_ (bytesize), stopbits_ (stopbits), flowcontrol_ (flowcontrol)
|
|
||||||
{
|
|
||||||
if (port_.empty () == false)
|
|
||||||
open ();
|
|
||||||
read_mutex = CreateMutex(NULL, false, NULL);
|
|
||||||
write_mutex = CreateMutex(NULL, false, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial::SerialImpl::~SerialImpl ()
|
|
||||||
{
|
|
||||||
this->close();
|
|
||||||
CloseHandle(read_mutex);
|
|
||||||
CloseHandle(write_mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::open ()
|
|
||||||
{
|
|
||||||
if (port_.empty ()) {
|
|
||||||
throw invalid_argument ("Empty port is invalid.");
|
|
||||||
}
|
|
||||||
if (is_open_ == true) {
|
|
||||||
throw SerialException ("Serial port already open.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// See: https://github.com/wjwwood/serial/issues/84
|
|
||||||
wstring port_with_prefix = _prefix_port_if_needed(port_);
|
|
||||||
LPCWSTR lp_port = port_with_prefix.c_str();
|
|
||||||
fd_ = CreateFileW(lp_port,
|
|
||||||
GENERIC_READ | GENERIC_WRITE,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
OPEN_EXISTING,
|
|
||||||
FILE_ATTRIBUTE_NORMAL,
|
|
||||||
0);
|
|
||||||
|
|
||||||
if (fd_ == INVALID_HANDLE_VALUE) {
|
|
||||||
DWORD create_file_err = GetLastError();
|
|
||||||
stringstream ss;
|
|
||||||
switch (create_file_err) {
|
|
||||||
case ERROR_FILE_NOT_FOUND:
|
|
||||||
// Use this->getPort to convert to a std::string
|
|
||||||
ss << "Specified port, " << this->getPort() << ", does not exist.";
|
|
||||||
THROW (IOException, ss.str().c_str());
|
|
||||||
default:
|
|
||||||
ss << "Unknown error opening the serial port: " << create_file_err;
|
|
||||||
THROW (IOException, ss.str().c_str());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
reconfigurePort();
|
|
||||||
is_open_ = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::reconfigurePort ()
|
|
||||||
{
|
|
||||||
if (fd_ == INVALID_HANDLE_VALUE) {
|
|
||||||
// Can only operate on a valid file descriptor
|
|
||||||
THROW (IOException, "Invalid file descriptor, is the serial port open?");
|
|
||||||
}
|
|
||||||
|
|
||||||
DCB dcbSerialParams = {0};
|
|
||||||
|
|
||||||
dcbSerialParams.DCBlength=sizeof(dcbSerialParams);
|
|
||||||
|
|
||||||
if (!GetCommState(fd_, &dcbSerialParams)) {
|
|
||||||
//error getting state
|
|
||||||
THROW (IOException, "Error getting the serial port state.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup baud rate
|
|
||||||
switch (baudrate_) {
|
|
||||||
#ifdef CBR_0
|
|
||||||
case 0: dcbSerialParams.BaudRate = CBR_0; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_50
|
|
||||||
case 50: dcbSerialParams.BaudRate = CBR_50; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_75
|
|
||||||
case 75: dcbSerialParams.BaudRate = CBR_75; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_110
|
|
||||||
case 110: dcbSerialParams.BaudRate = CBR_110; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_134
|
|
||||||
case 134: dcbSerialParams.BaudRate = CBR_134; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_150
|
|
||||||
case 150: dcbSerialParams.BaudRate = CBR_150; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_200
|
|
||||||
case 200: dcbSerialParams.BaudRate = CBR_200; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_300
|
|
||||||
case 300: dcbSerialParams.BaudRate = CBR_300; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_600
|
|
||||||
case 600: dcbSerialParams.BaudRate = CBR_600; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_1200
|
|
||||||
case 1200: dcbSerialParams.BaudRate = CBR_1200; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_1800
|
|
||||||
case 1800: dcbSerialParams.BaudRate = CBR_1800; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_2400
|
|
||||||
case 2400: dcbSerialParams.BaudRate = CBR_2400; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_4800
|
|
||||||
case 4800: dcbSerialParams.BaudRate = CBR_4800; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_7200
|
|
||||||
case 7200: dcbSerialParams.BaudRate = CBR_7200; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_9600
|
|
||||||
case 9600: dcbSerialParams.BaudRate = CBR_9600; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_14400
|
|
||||||
case 14400: dcbSerialParams.BaudRate = CBR_14400; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_19200
|
|
||||||
case 19200: dcbSerialParams.BaudRate = CBR_19200; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_28800
|
|
||||||
case 28800: dcbSerialParams.BaudRate = CBR_28800; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_57600
|
|
||||||
case 57600: dcbSerialParams.BaudRate = CBR_57600; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_76800
|
|
||||||
case 76800: dcbSerialParams.BaudRate = CBR_76800; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_38400
|
|
||||||
case 38400: dcbSerialParams.BaudRate = CBR_38400; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_115200
|
|
||||||
case 115200: dcbSerialParams.BaudRate = CBR_115200; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_128000
|
|
||||||
case 128000: dcbSerialParams.BaudRate = CBR_128000; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_153600
|
|
||||||
case 153600: dcbSerialParams.BaudRate = CBR_153600; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_230400
|
|
||||||
case 230400: dcbSerialParams.BaudRate = CBR_230400; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_256000
|
|
||||||
case 256000: dcbSerialParams.BaudRate = CBR_256000; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_460800
|
|
||||||
case 460800: dcbSerialParams.BaudRate = CBR_460800; break;
|
|
||||||
#endif
|
|
||||||
#ifdef CBR_921600
|
|
||||||
case 921600: dcbSerialParams.BaudRate = CBR_921600; break;
|
|
||||||
#endif
|
|
||||||
default:
|
|
||||||
// Try to blindly assign it
|
|
||||||
dcbSerialParams.BaudRate = baudrate_;
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup char len
|
|
||||||
if (bytesize_ == eightbits)
|
|
||||||
dcbSerialParams.ByteSize = 8;
|
|
||||||
else if (bytesize_ == sevenbits)
|
|
||||||
dcbSerialParams.ByteSize = 7;
|
|
||||||
else if (bytesize_ == sixbits)
|
|
||||||
dcbSerialParams.ByteSize = 6;
|
|
||||||
else if (bytesize_ == fivebits)
|
|
||||||
dcbSerialParams.ByteSize = 5;
|
|
||||||
else
|
|
||||||
throw invalid_argument ("invalid char len");
|
|
||||||
|
|
||||||
// setup stopbits
|
|
||||||
if (stopbits_ == stopbits_one)
|
|
||||||
dcbSerialParams.StopBits = ONESTOPBIT;
|
|
||||||
else if (stopbits_ == stopbits_one_point_five)
|
|
||||||
dcbSerialParams.StopBits = ONE5STOPBITS;
|
|
||||||
else if (stopbits_ == stopbits_two)
|
|
||||||
dcbSerialParams.StopBits = TWOSTOPBITS;
|
|
||||||
else
|
|
||||||
throw invalid_argument ("invalid stop bit");
|
|
||||||
|
|
||||||
// setup parity
|
|
||||||
if (parity_ == parity_none) {
|
|
||||||
dcbSerialParams.Parity = NOPARITY;
|
|
||||||
} else if (parity_ == parity_even) {
|
|
||||||
dcbSerialParams.Parity = EVENPARITY;
|
|
||||||
} else if (parity_ == parity_odd) {
|
|
||||||
dcbSerialParams.Parity = ODDPARITY;
|
|
||||||
} else if (parity_ == parity_mark) {
|
|
||||||
dcbSerialParams.Parity = MARKPARITY;
|
|
||||||
} else if (parity_ == parity_space) {
|
|
||||||
dcbSerialParams.Parity = SPACEPARITY;
|
|
||||||
} else {
|
|
||||||
throw invalid_argument ("invalid parity");
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup flowcontrol
|
|
||||||
if (flowcontrol_ == flowcontrol_none) {
|
|
||||||
dcbSerialParams.fOutxCtsFlow = false;
|
|
||||||
dcbSerialParams.fRtsControl = RTS_CONTROL_DISABLE;
|
|
||||||
dcbSerialParams.fOutX = false;
|
|
||||||
dcbSerialParams.fInX = false;
|
|
||||||
}
|
|
||||||
if (flowcontrol_ == flowcontrol_software) {
|
|
||||||
dcbSerialParams.fOutxCtsFlow = false;
|
|
||||||
dcbSerialParams.fRtsControl = RTS_CONTROL_DISABLE;
|
|
||||||
dcbSerialParams.fOutX = true;
|
|
||||||
dcbSerialParams.fInX = true;
|
|
||||||
}
|
|
||||||
if (flowcontrol_ == flowcontrol_hardware) {
|
|
||||||
dcbSerialParams.fOutxCtsFlow = true;
|
|
||||||
dcbSerialParams.fRtsControl = RTS_CONTROL_HANDSHAKE;
|
|
||||||
dcbSerialParams.fOutX = false;
|
|
||||||
dcbSerialParams.fInX = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// activate settings
|
|
||||||
if (!SetCommState(fd_, &dcbSerialParams)){
|
|
||||||
CloseHandle(fd_);
|
|
||||||
THROW (IOException, "Error setting serial port settings.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup timeouts
|
|
||||||
COMMTIMEOUTS timeouts = {0};
|
|
||||||
timeouts.ReadIntervalTimeout = timeout_.inter_byte_timeout;
|
|
||||||
timeouts.ReadTotalTimeoutConstant = timeout_.read_timeout_constant;
|
|
||||||
timeouts.ReadTotalTimeoutMultiplier = timeout_.read_timeout_multiplier;
|
|
||||||
timeouts.WriteTotalTimeoutConstant = timeout_.write_timeout_constant;
|
|
||||||
timeouts.WriteTotalTimeoutMultiplier = timeout_.write_timeout_multiplier;
|
|
||||||
if (!SetCommTimeouts(fd_, &timeouts)) {
|
|
||||||
THROW (IOException, "Error setting timeouts.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::close ()
|
|
||||||
{
|
|
||||||
if (is_open_ == true) {
|
|
||||||
if (fd_ != INVALID_HANDLE_VALUE) {
|
|
||||||
int ret;
|
|
||||||
ret = CloseHandle(fd_);
|
|
||||||
if (ret == 0) {
|
|
||||||
stringstream ss;
|
|
||||||
ss << "Error while closing serial port: " << GetLastError();
|
|
||||||
THROW (IOException, ss.str().c_str());
|
|
||||||
} else {
|
|
||||||
fd_ = INVALID_HANDLE_VALUE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is_open_ = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
Serial::SerialImpl::isOpen () const
|
|
||||||
{
|
|
||||||
return is_open_;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
Serial::SerialImpl::available ()
|
|
||||||
{
|
|
||||||
if (!is_open_) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
COMSTAT cs;
|
|
||||||
if (!ClearCommError(fd_, NULL, &cs)) {
|
|
||||||
stringstream ss;
|
|
||||||
ss << "Error while checking status of the serial port: " << GetLastError();
|
|
||||||
THROW (IOException, ss.str().c_str());
|
|
||||||
}
|
|
||||||
return static_cast<size_t>(cs.cbInQue);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
Serial::SerialImpl::waitReadable (uint32_t /*timeout*/)
|
|
||||||
{
|
|
||||||
THROW (IOException, "waitReadable is not implemented on Windows.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::waitByteTimes (size_t /*count*/)
|
|
||||||
{
|
|
||||||
THROW (IOException, "waitByteTimes is not implemented on Windows.");
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
Serial::SerialImpl::read (uint8_t *buf, size_t size)
|
|
||||||
{
|
|
||||||
if (!is_open_) {
|
|
||||||
throw PortNotOpenedException ("Serial::read");
|
|
||||||
}
|
|
||||||
DWORD bytes_read;
|
|
||||||
if (!ReadFile(fd_, buf, static_cast<DWORD>(size), &bytes_read, NULL)) {
|
|
||||||
stringstream ss;
|
|
||||||
ss << "Error while reading from the serial port: " << GetLastError();
|
|
||||||
THROW (IOException, ss.str().c_str());
|
|
||||||
}
|
|
||||||
return (size_t) (bytes_read);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
Serial::SerialImpl::write (const uint8_t *data, size_t length)
|
|
||||||
{
|
|
||||||
if (is_open_ == false) {
|
|
||||||
throw PortNotOpenedException ("Serial::write");
|
|
||||||
}
|
|
||||||
DWORD bytes_written;
|
|
||||||
if (!WriteFile(fd_, data, static_cast<DWORD>(length), &bytes_written, NULL)) {
|
|
||||||
stringstream ss;
|
|
||||||
ss << "Error while writing to the serial port: " << GetLastError();
|
|
||||||
THROW (IOException, ss.str().c_str());
|
|
||||||
}
|
|
||||||
return (size_t) (bytes_written);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::setPort (const string &port)
|
|
||||||
{
|
|
||||||
port_ = wstring(port.begin(), port.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
Serial::SerialImpl::getPort () const
|
|
||||||
{
|
|
||||||
return string(port_.begin(), port_.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::setTimeout (serial::Timeout &timeout)
|
|
||||||
{
|
|
||||||
timeout_ = timeout;
|
|
||||||
if (is_open_) {
|
|
||||||
reconfigurePort ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serial::Timeout
|
|
||||||
Serial::SerialImpl::getTimeout () const
|
|
||||||
{
|
|
||||||
return timeout_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::setBaudrate (unsigned long baudrate)
|
|
||||||
{
|
|
||||||
baudrate_ = baudrate;
|
|
||||||
if (is_open_) {
|
|
||||||
reconfigurePort ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long
|
|
||||||
Serial::SerialImpl::getBaudrate () const
|
|
||||||
{
|
|
||||||
return baudrate_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::setBytesize (serial::bytesize_t bytesize)
|
|
||||||
{
|
|
||||||
bytesize_ = bytesize;
|
|
||||||
if (is_open_) {
|
|
||||||
reconfigurePort ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serial::bytesize_t
|
|
||||||
Serial::SerialImpl::getBytesize () const
|
|
||||||
{
|
|
||||||
return bytesize_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::setParity (serial::parity_t parity)
|
|
||||||
{
|
|
||||||
parity_ = parity;
|
|
||||||
if (is_open_) {
|
|
||||||
reconfigurePort ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serial::parity_t
|
|
||||||
Serial::SerialImpl::getParity () const
|
|
||||||
{
|
|
||||||
return parity_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::setStopbits (serial::stopbits_t stopbits)
|
|
||||||
{
|
|
||||||
stopbits_ = stopbits;
|
|
||||||
if (is_open_) {
|
|
||||||
reconfigurePort ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serial::stopbits_t
|
|
||||||
Serial::SerialImpl::getStopbits () const
|
|
||||||
{
|
|
||||||
return stopbits_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::setFlowcontrol (serial::flowcontrol_t flowcontrol)
|
|
||||||
{
|
|
||||||
flowcontrol_ = flowcontrol;
|
|
||||||
if (is_open_) {
|
|
||||||
reconfigurePort ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
serial::flowcontrol_t
|
|
||||||
Serial::SerialImpl::getFlowcontrol () const
|
|
||||||
{
|
|
||||||
return flowcontrol_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::flush ()
|
|
||||||
{
|
|
||||||
if (is_open_ == false) {
|
|
||||||
throw PortNotOpenedException ("Serial::flush");
|
|
||||||
}
|
|
||||||
FlushFileBuffers (fd_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::flushInput ()
|
|
||||||
{
|
|
||||||
if (is_open_ == false) {
|
|
||||||
throw PortNotOpenedException("Serial::flushInput");
|
|
||||||
}
|
|
||||||
PurgeComm(fd_, PURGE_RXCLEAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::flushOutput ()
|
|
||||||
{
|
|
||||||
if (is_open_ == false) {
|
|
||||||
throw PortNotOpenedException("Serial::flushOutput");
|
|
||||||
}
|
|
||||||
PurgeComm(fd_, PURGE_TXCLEAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::sendBreak (int /*duration*/)
|
|
||||||
{
|
|
||||||
THROW (IOException, "sendBreak is not supported on Windows.");
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::setBreak (bool level)
|
|
||||||
{
|
|
||||||
if (is_open_ == false) {
|
|
||||||
throw PortNotOpenedException ("Serial::setBreak");
|
|
||||||
}
|
|
||||||
if (level) {
|
|
||||||
EscapeCommFunction (fd_, SETBREAK);
|
|
||||||
} else {
|
|
||||||
EscapeCommFunction (fd_, CLRBREAK);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::setRTS (bool level)
|
|
||||||
{
|
|
||||||
if (is_open_ == false) {
|
|
||||||
throw PortNotOpenedException ("Serial::setRTS");
|
|
||||||
}
|
|
||||||
if (level) {
|
|
||||||
EscapeCommFunction (fd_, SETRTS);
|
|
||||||
} else {
|
|
||||||
EscapeCommFunction (fd_, CLRRTS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::setDTR (bool level)
|
|
||||||
{
|
|
||||||
if (is_open_ == false) {
|
|
||||||
throw PortNotOpenedException ("Serial::setDTR");
|
|
||||||
}
|
|
||||||
if (level) {
|
|
||||||
EscapeCommFunction (fd_, SETDTR);
|
|
||||||
} else {
|
|
||||||
EscapeCommFunction (fd_, CLRDTR);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
Serial::SerialImpl::waitForChange ()
|
|
||||||
{
|
|
||||||
if (is_open_ == false) {
|
|
||||||
throw PortNotOpenedException ("Serial::waitForChange");
|
|
||||||
}
|
|
||||||
DWORD dwCommEvent;
|
|
||||||
|
|
||||||
if (!SetCommMask(fd_, EV_CTS | EV_DSR | EV_RING | EV_RLSD)) {
|
|
||||||
// Error setting communications mask
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!WaitCommEvent(fd_, &dwCommEvent, NULL)) {
|
|
||||||
// An error occurred waiting for the event.
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
// Event has occurred.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
Serial::SerialImpl::getCTS ()
|
|
||||||
{
|
|
||||||
if (is_open_ == false) {
|
|
||||||
throw PortNotOpenedException ("Serial::getCTS");
|
|
||||||
}
|
|
||||||
DWORD dwModemStatus;
|
|
||||||
if (!GetCommModemStatus(fd_, &dwModemStatus)) {
|
|
||||||
THROW (IOException, "Error getting the status of the CTS line.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (MS_CTS_ON & dwModemStatus) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
Serial::SerialImpl::getDSR ()
|
|
||||||
{
|
|
||||||
if (is_open_ == false) {
|
|
||||||
throw PortNotOpenedException ("Serial::getDSR");
|
|
||||||
}
|
|
||||||
DWORD dwModemStatus;
|
|
||||||
if (!GetCommModemStatus(fd_, &dwModemStatus)) {
|
|
||||||
THROW (IOException, "Error getting the status of the DSR line.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (MS_DSR_ON & dwModemStatus) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
Serial::SerialImpl::getRI()
|
|
||||||
{
|
|
||||||
if (is_open_ == false) {
|
|
||||||
throw PortNotOpenedException ("Serial::getRI");
|
|
||||||
}
|
|
||||||
DWORD dwModemStatus;
|
|
||||||
if (!GetCommModemStatus(fd_, &dwModemStatus)) {
|
|
||||||
THROW (IOException, "Error getting the status of the RI line.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (MS_RING_ON & dwModemStatus) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
Serial::SerialImpl::getCD()
|
|
||||||
{
|
|
||||||
if (is_open_ == false) {
|
|
||||||
throw PortNotOpenedException ("Serial::getCD");
|
|
||||||
}
|
|
||||||
DWORD dwModemStatus;
|
|
||||||
if (!GetCommModemStatus(fd_, &dwModemStatus)) {
|
|
||||||
// Error in GetCommModemStatus;
|
|
||||||
THROW (IOException, "Error getting the status of the CD line.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return (MS_RLSD_ON & dwModemStatus) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::readLock()
|
|
||||||
{
|
|
||||||
if (WaitForSingleObject(read_mutex, INFINITE) != WAIT_OBJECT_0) {
|
|
||||||
THROW (IOException, "Error claiming read mutex.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::readUnlock()
|
|
||||||
{
|
|
||||||
if (!ReleaseMutex(read_mutex)) {
|
|
||||||
THROW (IOException, "Error releasing read mutex.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::writeLock()
|
|
||||||
{
|
|
||||||
if (WaitForSingleObject(write_mutex, INFINITE) != WAIT_OBJECT_0) {
|
|
||||||
THROW (IOException, "Error claiming write mutex.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::SerialImpl::writeUnlock()
|
|
||||||
{
|
|
||||||
if (!ReleaseMutex(write_mutex)) {
|
|
||||||
THROW (IOException, "Error releasing write mutex.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // #if defined(_WIN32)
|
|
||||||
|
|
||||||
@@ -1,432 +0,0 @@
|
|||||||
/* Copyright 2012 William Woodall and John Harrison */
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#if !defined(_WIN32) && !defined(__OpenBSD__) && !defined(__FreeBSD__)
|
|
||||||
# include <alloca.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined (__MINGW32__)
|
|
||||||
# define alloca __builtin_alloca
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "serial/serial.h"
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include "serial/impl/win.h"
|
|
||||||
#else
|
|
||||||
#include "serial/impl/unix.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
using std::invalid_argument;
|
|
||||||
using std::min;
|
|
||||||
using std::numeric_limits;
|
|
||||||
using std::vector;
|
|
||||||
using std::size_t;
|
|
||||||
using std::string;
|
|
||||||
|
|
||||||
using serial::Serial;
|
|
||||||
using serial::SerialException;
|
|
||||||
using serial::IOException;
|
|
||||||
using serial::bytesize_t;
|
|
||||||
using serial::parity_t;
|
|
||||||
using serial::stopbits_t;
|
|
||||||
using serial::flowcontrol_t;
|
|
||||||
|
|
||||||
class Serial::ScopedReadLock {
|
|
||||||
public:
|
|
||||||
ScopedReadLock(SerialImpl *pimpl) : pimpl_(pimpl) {
|
|
||||||
this->pimpl_->readLock();
|
|
||||||
}
|
|
||||||
~ScopedReadLock() {
|
|
||||||
this->pimpl_->readUnlock();
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
// Disable copy constructors
|
|
||||||
ScopedReadLock(const ScopedReadLock&);
|
|
||||||
const ScopedReadLock& operator=(ScopedReadLock);
|
|
||||||
|
|
||||||
SerialImpl *pimpl_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class Serial::ScopedWriteLock {
|
|
||||||
public:
|
|
||||||
ScopedWriteLock(SerialImpl *pimpl) : pimpl_(pimpl) {
|
|
||||||
this->pimpl_->writeLock();
|
|
||||||
}
|
|
||||||
~ScopedWriteLock() {
|
|
||||||
this->pimpl_->writeUnlock();
|
|
||||||
}
|
|
||||||
private:
|
|
||||||
// Disable copy constructors
|
|
||||||
ScopedWriteLock(const ScopedWriteLock&);
|
|
||||||
const ScopedWriteLock& operator=(ScopedWriteLock);
|
|
||||||
SerialImpl *pimpl_;
|
|
||||||
};
|
|
||||||
|
|
||||||
Serial::Serial (const string &port, uint32_t baudrate, serial::Timeout timeout,
|
|
||||||
bytesize_t bytesize, parity_t parity, stopbits_t stopbits,
|
|
||||||
flowcontrol_t flowcontrol)
|
|
||||||
: pimpl_(new SerialImpl (port, baudrate, bytesize, parity,
|
|
||||||
stopbits, flowcontrol))
|
|
||||||
{
|
|
||||||
pimpl_->setTimeout(timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial::~Serial ()
|
|
||||||
{
|
|
||||||
delete pimpl_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::open ()
|
|
||||||
{
|
|
||||||
pimpl_->open ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::close ()
|
|
||||||
{
|
|
||||||
pimpl_->close ();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
Serial::isOpen () const
|
|
||||||
{
|
|
||||||
return pimpl_->isOpen ();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
Serial::available ()
|
|
||||||
{
|
|
||||||
return pimpl_->available ();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
Serial::waitReadable ()
|
|
||||||
{
|
|
||||||
serial::Timeout timeout(pimpl_->getTimeout ());
|
|
||||||
return pimpl_->waitReadable(timeout.read_timeout_constant);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::waitByteTimes (size_t count)
|
|
||||||
{
|
|
||||||
pimpl_->waitByteTimes(count);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
Serial::read_ (uint8_t *buffer, size_t size)
|
|
||||||
{
|
|
||||||
return this->pimpl_->read (buffer, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
Serial::read (uint8_t *buffer, size_t size)
|
|
||||||
{
|
|
||||||
ScopedReadLock lock(this->pimpl_);
|
|
||||||
return this->pimpl_->read (buffer, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
Serial::read (std::vector<uint8_t> &buffer, size_t size)
|
|
||||||
{
|
|
||||||
ScopedReadLock lock(this->pimpl_);
|
|
||||||
uint8_t *buffer_ = new uint8_t[size];
|
|
||||||
size_t bytes_read = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
bytes_read = this->pimpl_->read (buffer_, size);
|
|
||||||
}
|
|
||||||
catch (const std::exception &e) {
|
|
||||||
delete[] buffer_;
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer.insert (buffer.end (), buffer_, buffer_+bytes_read);
|
|
||||||
delete[] buffer_;
|
|
||||||
return bytes_read;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
Serial::read (std::string &buffer, size_t size)
|
|
||||||
{
|
|
||||||
ScopedReadLock lock(this->pimpl_);
|
|
||||||
uint8_t *buffer_ = new uint8_t[size];
|
|
||||||
size_t bytes_read = 0;
|
|
||||||
try {
|
|
||||||
bytes_read = this->pimpl_->read (buffer_, size);
|
|
||||||
}
|
|
||||||
catch (const std::exception &e) {
|
|
||||||
delete[] buffer_;
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
buffer.append (reinterpret_cast<const char*>(buffer_), bytes_read);
|
|
||||||
delete[] buffer_;
|
|
||||||
return bytes_read;
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
Serial::read (size_t size)
|
|
||||||
{
|
|
||||||
std::string buffer;
|
|
||||||
this->read (buffer, size);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
Serial::readline (string &buffer, size_t size, string eol)
|
|
||||||
{
|
|
||||||
ScopedReadLock lock(this->pimpl_);
|
|
||||||
size_t eol_len = eol.length ();
|
|
||||||
uint8_t *buffer_ = static_cast<uint8_t*>
|
|
||||||
(alloca (size * sizeof (uint8_t)));
|
|
||||||
size_t read_so_far = 0;
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
size_t bytes_read = this->read_ (buffer_ + read_so_far, 1);
|
|
||||||
read_so_far += bytes_read;
|
|
||||||
if (bytes_read == 0) {
|
|
||||||
break; // Timeout occured on reading 1 byte
|
|
||||||
}
|
|
||||||
if(read_so_far < eol_len) continue;
|
|
||||||
if (string (reinterpret_cast<const char*>
|
|
||||||
(buffer_ + read_so_far - eol_len), eol_len) == eol) {
|
|
||||||
break; // EOL found
|
|
||||||
}
|
|
||||||
if (read_so_far == size) {
|
|
||||||
break; // Reached the maximum read length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer.append(reinterpret_cast<const char*> (buffer_), read_so_far);
|
|
||||||
return read_so_far;
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
Serial::readline (size_t size, string eol)
|
|
||||||
{
|
|
||||||
std::string buffer;
|
|
||||||
this->readline (buffer, size, eol);
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
vector<string>
|
|
||||||
Serial::readlines (size_t size, string eol)
|
|
||||||
{
|
|
||||||
ScopedReadLock lock(this->pimpl_);
|
|
||||||
std::vector<std::string> lines;
|
|
||||||
size_t eol_len = eol.length ();
|
|
||||||
uint8_t *buffer_ = static_cast<uint8_t*>
|
|
||||||
(alloca (size * sizeof (uint8_t)));
|
|
||||||
size_t read_so_far = 0;
|
|
||||||
size_t start_of_line = 0;
|
|
||||||
while (read_so_far < size) {
|
|
||||||
size_t bytes_read = this->read_ (buffer_+read_so_far, 1);
|
|
||||||
read_so_far += bytes_read;
|
|
||||||
if (bytes_read == 0) {
|
|
||||||
if (start_of_line != read_so_far) {
|
|
||||||
lines.push_back (
|
|
||||||
string (reinterpret_cast<const char*> (buffer_ + start_of_line),
|
|
||||||
read_so_far - start_of_line));
|
|
||||||
}
|
|
||||||
break; // Timeout occured on reading 1 byte
|
|
||||||
}
|
|
||||||
if(read_so_far < eol_len) continue;
|
|
||||||
if (string (reinterpret_cast<const char*>
|
|
||||||
(buffer_ + read_so_far - eol_len), eol_len) == eol) {
|
|
||||||
// EOL found
|
|
||||||
lines.push_back(
|
|
||||||
string(reinterpret_cast<const char*> (buffer_ + start_of_line),
|
|
||||||
read_so_far - start_of_line));
|
|
||||||
start_of_line = read_so_far;
|
|
||||||
}
|
|
||||||
if (read_so_far == size) {
|
|
||||||
if (start_of_line != read_so_far) {
|
|
||||||
lines.push_back(
|
|
||||||
string(reinterpret_cast<const char*> (buffer_ + start_of_line),
|
|
||||||
read_so_far - start_of_line));
|
|
||||||
}
|
|
||||||
break; // Reached the maximum read length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
Serial::write (const string &data)
|
|
||||||
{
|
|
||||||
ScopedWriteLock lock(this->pimpl_);
|
|
||||||
return this->write_ (reinterpret_cast<const uint8_t*>(data.c_str()),
|
|
||||||
data.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
Serial::write (const std::vector<uint8_t> &data)
|
|
||||||
{
|
|
||||||
ScopedWriteLock lock(this->pimpl_);
|
|
||||||
return this->write_ (&data[0], data.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
Serial::write (const uint8_t *data, size_t size)
|
|
||||||
{
|
|
||||||
ScopedWriteLock lock(this->pimpl_);
|
|
||||||
return this->write_(data, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t
|
|
||||||
Serial::write_ (const uint8_t *data, size_t length)
|
|
||||||
{
|
|
||||||
return pimpl_->write (data, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::setPort (const string &port)
|
|
||||||
{
|
|
||||||
ScopedReadLock rlock(this->pimpl_);
|
|
||||||
ScopedWriteLock wlock(this->pimpl_);
|
|
||||||
bool was_open = pimpl_->isOpen ();
|
|
||||||
if (was_open) close();
|
|
||||||
pimpl_->setPort (port);
|
|
||||||
if (was_open) open ();
|
|
||||||
}
|
|
||||||
|
|
||||||
string
|
|
||||||
Serial::getPort () const
|
|
||||||
{
|
|
||||||
return pimpl_->getPort ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::setTimeout (serial::Timeout &timeout)
|
|
||||||
{
|
|
||||||
pimpl_->setTimeout (timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
serial::Timeout
|
|
||||||
Serial::getTimeout () const {
|
|
||||||
return pimpl_->getTimeout ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::setBaudrate (uint32_t baudrate)
|
|
||||||
{
|
|
||||||
pimpl_->setBaudrate (baudrate);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t
|
|
||||||
Serial::getBaudrate () const
|
|
||||||
{
|
|
||||||
return uint32_t(pimpl_->getBaudrate ());
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::setBytesize (bytesize_t bytesize)
|
|
||||||
{
|
|
||||||
pimpl_->setBytesize (bytesize);
|
|
||||||
}
|
|
||||||
|
|
||||||
bytesize_t
|
|
||||||
Serial::getBytesize () const
|
|
||||||
{
|
|
||||||
return pimpl_->getBytesize ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::setParity (parity_t parity)
|
|
||||||
{
|
|
||||||
pimpl_->setParity (parity);
|
|
||||||
}
|
|
||||||
|
|
||||||
parity_t
|
|
||||||
Serial::getParity () const
|
|
||||||
{
|
|
||||||
return pimpl_->getParity ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::setStopbits (stopbits_t stopbits)
|
|
||||||
{
|
|
||||||
pimpl_->setStopbits (stopbits);
|
|
||||||
}
|
|
||||||
|
|
||||||
stopbits_t
|
|
||||||
Serial::getStopbits () const
|
|
||||||
{
|
|
||||||
return pimpl_->getStopbits ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Serial::setFlowcontrol (flowcontrol_t flowcontrol)
|
|
||||||
{
|
|
||||||
pimpl_->setFlowcontrol (flowcontrol);
|
|
||||||
}
|
|
||||||
|
|
||||||
flowcontrol_t
|
|
||||||
Serial::getFlowcontrol () const
|
|
||||||
{
|
|
||||||
return pimpl_->getFlowcontrol ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Serial::flush ()
|
|
||||||
{
|
|
||||||
ScopedReadLock rlock(this->pimpl_);
|
|
||||||
ScopedWriteLock wlock(this->pimpl_);
|
|
||||||
pimpl_->flush ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Serial::flushInput ()
|
|
||||||
{
|
|
||||||
ScopedReadLock lock(this->pimpl_);
|
|
||||||
pimpl_->flushInput ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Serial::flushOutput ()
|
|
||||||
{
|
|
||||||
ScopedWriteLock lock(this->pimpl_);
|
|
||||||
pimpl_->flushOutput ();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Serial::sendBreak (int duration)
|
|
||||||
{
|
|
||||||
pimpl_->sendBreak (duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Serial::setBreak (bool level)
|
|
||||||
{
|
|
||||||
pimpl_->setBreak (level);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Serial::setRTS (bool level)
|
|
||||||
{
|
|
||||||
pimpl_->setRTS (level);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Serial::setDTR (bool level)
|
|
||||||
{
|
|
||||||
pimpl_->setDTR (level);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Serial::waitForChange()
|
|
||||||
{
|
|
||||||
return pimpl_->waitForChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Serial::getCTS ()
|
|
||||||
{
|
|
||||||
return pimpl_->getCTS ();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Serial::getDSR ()
|
|
||||||
{
|
|
||||||
return pimpl_->getDSR ();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Serial::getRI ()
|
|
||||||
{
|
|
||||||
return pimpl_->getRI ();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Serial::getCD ()
|
|
||||||
{
|
|
||||||
return pimpl_->getCD ();
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user