diff --git a/CMakeLists.txt b/CMakeLists.txt index 96744a3..966cb6a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,9 @@ add_library(edge_proxy_lib STATIC #SQL src/dataStorage/data_storage.cc + + #tts + src/tts/piper_tts_interface.cc ) target_include_directories(edge_proxy_lib PUBLIC @@ -82,7 +85,7 @@ target_link_libraries(edge_proxy_lib PRIVATE SQLite::SQLite3 pthread nlohmann_json - rknn_api + # rknn_api rknnrt ) @@ -103,15 +106,15 @@ target_link_libraries(edge_proxy PRIVATE # =================#================================================ # 测试目标 # ================================================================= -# add_executable(test -# src/test.cc -# src/piper_tts_interface.cc +add_executable(test + src/test.cc + src/systemMonitor/iio_sensor.cc -# ) +) -# target_link_libraries(test PRIVATE -# edge_proxy_lib -# ) +target_link_libraries(test PRIVATE + edge_proxy_lib +) # 1. 定义新的可执行文件 (我们将把代码放在 src/streamer/ 目录下) diff --git a/docker-compose.yml b/docker-compose.yml index c836fdb..8c0034f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,8 @@ services: - /dev/rga:/dev/rga - /dev/dri:/dev/dri + - source: /sys/bus/iio/devices/iio:device0 + target: /sys/bus/iio/devices/iio:device0 group_add: - "20" - "44" diff --git a/src/systemMonitor/iio_sensor.cc b/src/systemMonitor/iio_sensor.cc new file mode 100644 index 0000000..9a8d7b3 --- /dev/null +++ b/src/systemMonitor/iio_sensor.cc @@ -0,0 +1,133 @@ +#include "iio_sensor.h" // 必须包含自己的头文件 +#include +#include +#include +#include +#include +#include + +// --- PIMPL 模式的具体实现 --- +// 这里我们只有一个空的实现,因为我们不再在类内部保存文件流 +// 但保留这个结构可以让我们在不改变.h文件的情况下未来扩展功能 +struct IioSensor::Impl { + // 当前实现为空 + // std::ifstream voltageFile; // 这种方式已被抛弃 +}; + +// --- 构造函数 --- +// 现在只负责构建路径和检查文件是否存在,不打开文件 +IioSensor::IioSensor(const std::string& devicePath, const std::string& channelName) + : devicePath_(devicePath), channelName_(channelName) { + + // 使用 std::filesystem 安全地拼接路径 + std::filesystem::path fullFilePath = std::filesystem::path(devicePath_) / channelName_; + fullFilePath_ = fullFilePath.string(); + + // 检查文件是否存在,这是一个非常好的防御性编程实践 + if (!std::filesystem::exists(fullFilePath_)) { + throw IioSensorError("IIO Channel Error: File not found at " + fullFilePath_ + "\n" + "Please check the path (" + devicePath_ + ") and ensure the sensor is connected and configured."); + } + // 初始化 PIMPL,即使为空,也需要创建 + pImpl_ = std::make_unique(); +} + +// --- 析构函数 --- +// std::unique_ptr 会自动为我们释放 Impl 对象的内存,无需手动删除 +IioSensor::~IioSensor() = default; + + +// --- 公共接口实现 --- + +int64_t IioSensor::readRawValue() { + // --- 1. 每次读取都创建一个新的文件流对象 --- + // 使用RAII管理这个临时文件流,函数结束时它会自动关闭 + std::ifstream voltageFile(fullFilePath_, std::ios::in); + + // --- 2. 检查文件是否成功打开 --- + if (!voltageFile.is_open()) { + throw IioSensorError("IIO Channel Error: Failed to open file " + fullFilePath_ + "\n" + "Check permissions (the process might need to be in the 'iio' group)."); + } + + // --- 3. 读取文件内容 --- + std::string voltageValueStr; + // std::getline 从文件流中读取一行 + if (!std::getline(voltageFile, voltageValueStr)) { + // 如果读取操作失败(例如文件为空,但根据IIO模型,这里更可能是其他问题) + throw IioSensorError("IIO Channel Error: Failed to read data from file " + fullFilePath_); + } + + // --- 4. 字符串到数字的转换 --- + try { + // std::stoll 是 C++11 引入的,安全地将字符串转换为 long long + // 对于ADC值,long long (int64_t) 通常更合适 + int64_t rawValue = std::stoll(voltageValueStr); + return rawValue; + + } catch (const std::invalid_argument& e) { + // 如果字符串不是一个有效的数字 + throw IioSensorError("IIO Channel Error: Invalid data format in file " + fullFilePath_ + + ". Content: '" + voltageValueStr + "'"); + } catch (const std::out_of_range& e) { + // 如果数字超出 long long 的表示范围 + throw IioSensorError("IIO Channel Error: Number out of range in file " + fullFilePath_); + } +} + +double IioSensor::readVoltage(double referenceVoltage, int64_t adcResolution, double gain) { + // --- 1. 调用 readRawValue() 来复用错误处理和读取逻辑 --- + // 这符合DRY (Don't Repeat Yourself) 原则 + int64_t rawValue = readRawValue(); + + // --- 2. 进行运行时参数检查 --- + // static_assert 不适用于运行时参数,我们使用 if 语句 + if (adcResolution <= 0) { + throw std::invalid_argument("ADC Resolution must be a positive number."); + } + if (referenceVoltage <= 0) { + throw std::invalid_argument("Reference Voltage must be a positive number."); + } + if (gain == 0.0) { + throw std::invalid_argument("Gain cannot be zero."); + } + + // --- 3. 计算电压并返回 --- + // 注意:这里的转换是基于原始值的,具体物理含义需要根据你的数据手册确定 + // (rawValue / Resolution) * ReferenceVoltage * Gain + return static_cast(rawValue) / static_cast(adcResolution) * referenceVoltage * gain; +} + +// int main() { +// // --- 配置参数 --- +// // !!! 修改这里以匹配你的设备和通道 !!! +// const std::string iio_device_path = "/sys/bus/iio/devices/iio:device0"; +// const std::string iio_channel_name = "in_voltage2_raw"; // 例如,连接到 IN2 通道 + +// const double adc_reference_voltage = 1.8; // ADC参考电压 +// const int64_t adc_resolution = 4096; // ADC分辨率 (e.g., 2^12 for 12-bit) +// const double signal_gain = 11.0; // 信号通路的增益 +// std::cout << "--- Starting IIO Sensor Reader (Version 2) ---" << std::endl; +// std::cout << "Reading from device: " << iio_device_path << std::endl; +// std::cout << "Reading channel: " << iio_channel_name << std::endl; +// std::cout << "-----------------------------------------------" << std::endl; + +// // 注意:传感器对象在 try 块外创建,如果构造失败(找不到文件),会立即被捕获 +// IioSensor sensor(iio_device_path, iio_channel_name); +// while (true) { +// try { +// // 封装后的调用方式非常简洁! +// // 每次调用 readVoltage,内部都会重新打开文件读取一次 +// double actual_voltage = sensor.readVoltage(adc_reference_voltage, adc_resolution, signal_gain); +// // 输出结果 +// std::cout << "Actual Voltage: " << actual_voltage << " V" << std::endl; +// } catch (const IioSensorError& e) { +// // 如果单次读取失败(例如文件内容格式错误、无权限等),打印错误但继续循环 +// std::cerr << "[!] Read Error: " << e.what() << std::endl; +// } + +// // 每秒钟读取一次 +// std::this_thread::sleep_for(std::chrono::seconds(1)); +// } +// return 0; +// } \ No newline at end of file diff --git a/src/systemMonitor/iio_sensor.h b/src/systemMonitor/iio_sensor.h new file mode 100644 index 0000000..339f050 --- /dev/null +++ b/src/systemMonitor/iio_sensor.h @@ -0,0 +1,79 @@ +#ifndef IIO_SENSOR_H +#define IIO_SENSOR_H + +#include +#include // For std::unique_ptr +#include +#include +#include + +// 自定义异常类,专门用于处理IIO相关的错误 +// 继承自 std::runtime_error,这样我们就可以用 catch(std::exception&) 来捕获它 +class IioSensorError : public std::runtime_error { +public: + // 使用父类的构造函数来初始化错误信息 + explicit IioSensorError(const std::string& message) : std::runtime_error(message) {} +}; + +/** + * @brief 一个用于从 Linux Industrial I/O (IIO) 子系统读取原始ADC值的类。 + * + * 这个类封装了与IIO设备文件交互的细节,包括路径构建、文件读取、错误处理 + * 和数据类型转换。在这种实现中,每次读取操作都会重新打开和关闭设备文件, + * 这对于 IIO “一次性快照”式的文件模型是最健壮和最简单的方式。 + */ +class IioSensor { +public: + /** + * @brief 构造函数,初始化IIO传感器读取器。 + * + * 它会验证设备路径和通道是否存在,但**不会**在此时打开文件。 + * 文件将在每次 readRawValue() 或 readVoltage() 调用时临时打开。 + * + * @param devicePath IIO设备的绝对路径 (e.g., "/sys/bus/iio/devices/iio:device0"). + * @param channelName 要读取的通道名 (e.g., "in_voltage2_raw"). + * @throws IioSensorError 如果设备路径或通道路径不存在。 + */ + IioSensor(const std::string& devicePath, const std::string& channelName); + + /** + * @brief 析构函数,当前为空,因为资源由RAII在每次读取时管理。 + */ + ~IioSensor(); + + /** + * @brief 从IIO通道读取一次原始ADC值。 + * + * 每次调用此函数都会打开、读取并关闭文件。 + * + * @return int64_t 读取到的原始整数值。 + * @throws IioSensorError 如果文件不存在、无法打开、读取失败或值无效。 + */ + int64_t readRawValue(); + + /** + * @brief 从IIO通道读取一次原始ADC值并转换为电压。 + * + * 内部会调用 `readRawValue()` 来获取原始数据。 + * + * @param referenceVoltage ADC的参考电压,单位为伏特 (e.g., 3.3, 1.8). + * @param adcResolution ADC的分辨率 (e.g., 4096 for 12-bit, 65536 for 16-bit). + * @param gain (可选) 信号通路的总增益 (e.g., 11 for 11x gain). + * @return double 计算出的电压值。 + * @throws IioSensorError 如果在读取值的过程中发生任何错误,或参数无效。 + */ + double readVoltage(double referenceVoltage, int64_t adcResolution, double gain = 1.0); + +private: + // --- 私有成员变量 --- + std::string devicePath_; // IIO设备路径 + std::string channelName_; // IIO通道名称 + std::string fullFilePath_; // 完整的IIO通道文件路径 + + // PIMPL 结构体,用来隐藏实现细节 + // 虽然我们不再在构造函数里打开文件,但保留它可以将来轻松扩展 + struct Impl; + std::unique_ptr pImpl_; +}; + +#endif // IIO_SENSOR_H diff --git a/src/test.cc b/src/test.cc index 6a9b3ae..d7709e0 100644 --- a/src/test.cc +++ b/src/test.cc @@ -1,16 +1,53 @@ -#include "piper_tts_interface.h" -#include -#include +// #include "piper_tts_interface.h" +// #include +// #include +// int main() { +// PiperTTSInterface tts_processor; +// // 示例 1: 只提供文本和输出文件名 +// std::string text1 = "你好,世界。温度传感器出现故障。只需输入文本即可。"; +// std::string file1 = "hello_world.wav"; +// if (tts_processor.text_to_speech(text1, file1)) { +// std::cout << "Audio for '" << text1 << "' generated successfully!" << std::endl; +// } else { +// std::cerr << "Failed to generate audio for '" << text1 << "'." << std::endl; +// } +// return 0; +// } +#include +#include +#include +#include "systemMonitor/iio_sensor.h" // 包含我们刚刚创建的头文件 int main() { - PiperTTSInterface tts_processor; - // 示例 1: 只提供文本和输出文件名 - std::string text1 = "你好,世界。温度传感器出现故障。只需输入文本即可。"; - std::string file1 = "hello_world.wav"; - if (tts_processor.text_to_speech(text1, file1)) { - std::cout << "Audio for '" << text1 << "' generated successfully!" << std::endl; - } else { - std::cerr << "Failed to generate audio for '" << text1 << "'." << std::endl; + // --- 配置参数 --- + // !!! 修改这里以匹配你的设备和通道 !!! + const std::string iio_device_path = "/sys/bus/iio/devices/iio:device0"; + const std::string iio_channel_name = "in_voltage2_raw"; // 例如,连接到 IN2 通道 + + const double adc_reference_voltage = 1.8; // ADC参考电压 + const int64_t adc_resolution = 4096; // ADC分辨率 (e.g., 2^12 for 12-bit) + const double signal_gain = 11.0; // 信号通路的增益 + std::cout << "--- Starting IIO Sensor Reader (Version 2) ---" << std::endl; + std::cout << "Reading from device: " << iio_device_path << std::endl; + std::cout << "Reading channel: " << iio_channel_name << std::endl; + std::cout << "-----------------------------------------------" << std::endl; + + // 注意:传感器对象在 try 块外创建,如果构造失败(找不到文件),会立即被捕获 + IioSensor sensor(iio_device_path, iio_channel_name); + while (true) { + try { + // 封装后的调用方式非常简洁! + // 每次调用 readVoltage,内部都会重新打开文件读取一次 + double actual_voltage = sensor.readVoltage(adc_reference_voltage, adc_resolution, signal_gain); + // 输出结果 + std::cout << "Actual Voltage: " << actual_voltage << " V" << std::endl; + } catch (const IioSensorError& e) { + // 如果单次读取失败(例如文件内容格式错误、无权限等),打印错误但继续循环 + std::cerr << "[!] Read Error: " << e.what() << std::endl; + } + + // 每秒钟读取一次 + std::this_thread::sleep_for(std::chrono::seconds(1)); } return 0; -} +} \ No newline at end of file