xiangrifeng 3 mesiacov pred
commit
12e0b981bf
7 zmenil súbory, kde vykonal 977 pridanie a 0 odobranie
  1. 147 0
      README.md
  2. 116 0
      README_CENTOS.md
  3. 38 0
      deploy_centos.sh
  4. 217 0
      parse_file.py
  5. 457 0
      query_device_filter.py
  6. 2 0
      requirements.txt
  7. 0 0
      景兴故障测点筛选.txt

+ 147 - 0
README.md

@@ -0,0 +1,147 @@
+# 景兴故障测点筛选工具
+
+本项目包含两个主要工具,用于处理振动数据和筛选故障测点。
+
+## 功能模块
+
+### 1. 振动数据文件解析工具 (parse_file.py)
+
+用于解析振动数据文件,支持从FDFS下载文件或从本地文件系统读取,解析数据并计算不对称性。
+
+**主要功能:**
+- 解析FDFS文件路径或本地文件
+- 支持JSON格式和压缩格式的数据解析
+- 计算振动数据的不对称性(abs正、abs负)
+
+### 2. 设备和通道筛选工具 (query_device_filter.py)
+
+用于从数据库中查询和筛选符合条件的设备和通道组合,筛选出故障测点。
+
+**筛选条件:**
+- task_id = 2
+- 对于每个device_id和channel_no的组合,取最新的10条记录(按generate_time)
+- 如果这10个value值中有超过3个值大于20,则记录该(device_id, channel_no)组合
+- 解析每个通道最近10条文件记录,计算不对称性
+
+**输出结果:**
+- 符合条件的设备ID、设备名称
+- 通道号、通道名称
+- 最近10条文件记录及其不对称性数据
+- 结果保存到带时间戳的文本文件中
+
+## 环境要求
+
+- Python 3.8.3 或更高版本
+- 支持 Windows 和 Linux (CentOS 7.9) 系统
+- MySQL数据库连接
+
+## 安装依赖
+
+### Windows 环境
+
+```bash
+pip install -r requirements.txt
+```
+
+### CentOS 7.9 环境
+
+详细部署说明请参考 [README_CENTOS.md](README_CENTOS.md)
+
+快速部署(使用脚本):
+```bash
+chmod +x deploy_centos.sh
+./deploy_centos.sh
+```
+
+或手动安装:
+```bash
+python3 -m pip install -r requirements.txt --user
+```
+
+## 使用方法
+
+### 解析振动数据文件
+
+```bash
+python parse_file.py <file_name>
+```
+
+**示例:**
+```bash
+# FDFS路径
+python parse_file.py group1/M00/4C/AD/wKgUZWlQzpKAcUtmAA03b1suhI44618492
+
+# 本地文件路径
+python parse_file.py /home/soft/data/fdfs/storage/data/4C/AD/wKgUZWlQzpKAcUtmAA03b1suhI44618492
+```
+
+**输出内容:**
+- 解析后的数据(JSON格式)
+- 数据类型和基本信息
+- 不对称性计算结果(abs正、abs负)
+
+### 查询和筛选故障测点
+
+```bash
+python query_device_filter.py
+```
+
+**输出内容:**
+- 符合条件的设备ID和通道组合列表
+- 每个组合的设备名称和通道名称
+- 最近10条文件记录及其不对称性数据
+- 结果会自动保存到 `query_result_YYYYMMDD_HHMMSS.txt` 文件中
+
+## 数据库配置
+
+- 主机: 127.0.0.1
+- 端口: 3306
+- 用户名: prod
+- 密码: hmdmxjIvfIjIoflL
+- 数据库: iot
+
+## 依赖项
+
+- pymysql==1.0.2 - MySQL数据库连接
+
+## 文件说明
+
+- `parse_file.py` - 振动数据文件解析工具
+- `query_device_filter.py` - 设备和通道筛选工具
+- `requirements.txt` - Python依赖包列表
+- `deploy_centos.sh` - CentOS 7.9 部署脚本
+- `README_CENTOS.md` - CentOS 7.9 详细部署说明
+- `iot.sql` - 数据库表结构(如适用)
+
+## 注意事项
+
+1. **文件路径**:parse_file.py 支持FDFS路径和本地路径,本地路径需要正确配置FDFS存储路径映射
+2. **数据库连接**:确保数据库服务正常运行且网络可达
+3. **编码设置**:脚本会自动处理Windows和Linux环境下的编码问题
+4. **结果文件**:query_device_filter.py 会在当前目录生成带时间戳的结果文件
+
+## 故障排查
+
+### 数据库连接问题
+
+如果出现数据库连接失败:
+- 检查数据库服务是否运行
+- 检查网络连通性:`ping 127.0.0.1`
+- 检查端口是否开放:`telnet 127.0.0.1 3306` 或 `nc -zv 127.0.0.1 3306`
+- 验证数据库用户名和密码是否正确
+
+### 模块未找到错误
+
+如果提示 `ModuleNotFoundError: No module named 'pymysql'`:
+```bash
+pip install pymysql==1.0.2
+# 或
+python3 -m pip install pymysql==1.0.2 --user
+```
+
+### 文件解析错误
+
+- 确保文件路径正确
+- 检查文件是否存在
+- 验证文件格式是否正确(支持JSON格式或zlib压缩格式)
+

+ 116 - 0
README_CENTOS.md

@@ -0,0 +1,116 @@
+# CentOS 7.9 部署说明
+
+## 环境要求
+
+- CentOS 7.9
+- Python 3.8.3 或更高版本
+- pip (Python包管理器)
+
+## 快速开始
+
+### 方法一:使用部署脚本(推荐)
+
+1. 将以下文件上传到CentOS服务器:
+   - `test_db_connection.py`
+   - `requirements.txt`
+   - `deploy_centos.sh`
+
+2. 赋予执行权限并运行:
+```bash
+chmod +x deploy_centos.sh
+./deploy_centos.sh
+```
+
+### 方法二:手动安装
+
+1. 检查Python版本:
+```bash
+python3 --version
+```
+确认版本为 3.8.3 或更高
+
+2. 安装依赖:
+```bash
+# 如果pip未安装,先安装pip
+python3 -m ensurepip --upgrade
+
+# 安装项目依赖
+python3 -m pip install -r requirements.txt --user
+```
+
+3. 运行测试脚本:
+```bash
+python3 test_db_connection.py
+```
+
+## 如果系统没有Python 3.8.3
+
+### 方式一:使用yum安装(如果可用)
+
+```bash
+sudo yum install python3 python3-pip
+```
+
+### 方式二:从源码编译安装Python 3.8.3
+
+```bash
+# 安装编译依赖
+sudo yum groupinstall -y "Development Tools"
+sudo yum install -y openssl-devel bzip2-devel libffi-devel zlib-devel readline-devel sqlite-devel
+
+# 下载Python 3.8.3源码
+cd /tmp
+wget https://www.python.org/ftp/python/3.8.3/Python-3.8.3.tgz
+tar xzf Python-3.8.3.tgz
+cd Python-3.8.3
+
+# 编译安装
+./configure --enable-optimizations
+make altinstall
+
+# 验证安装
+python3.8 --version
+```
+
+## 数据库配置
+
+脚本会连接到以下数据库:
+- 主机: 127.0.0.1
+- 端口: 3306
+- 用户名: prod
+- 密码: hmdmxjIvfIjIoflL
+
+## 故障排查
+
+### 1. 连接超时
+
+如果出现连接超时错误,请检查:
+- 网络连通性:`ping 127.0.0.1`
+- 端口是否开放:`telnet 127.0.0.1 3306` 或 `nc -zv 127.0.0.1 3306`
+- 防火墙设置:确保3306端口未被阻止
+
+### 2. 模块未找到错误
+
+如果提示 `ModuleNotFoundError: No module named 'pymysql'`:
+```bash
+python3 -m pip install pymysql==1.0.2 --user
+```
+
+### 3. 权限问题
+
+如果使用 `--user` 标志安装仍然有问题,可以使用sudo(不推荐用于生产环境):
+```bash
+sudo python3 -m pip install -r requirements.txt
+```
+
+## 测试结果说明
+
+- **成功**: 会显示数据库版本、当前数据库名称和连接ID
+- **失败**: 会显示错误代码和详细错误信息
+
+## 注意事项
+
+- 确保CentOS服务器能够访问数据库服务器(127.0.0.1,本地数据库)
+- 如果使用防火墙,需要开放相应端口
+- 生产环境建议使用虚拟环境(venv)来隔离依赖
+

+ 38 - 0
deploy_centos.sh

@@ -0,0 +1,38 @@
+#!/bin/bash
+# CentOS 7.9 部署脚本
+# 用于在CentOS服务器上安装Python依赖并运行数据库连接测试
+
+echo "=========================================="
+echo "CentOS 7.9 数据库连接测试部署脚本"
+echo "=========================================="
+
+# 检查Python版本
+echo "检查Python版本..."
+python3 --version
+if [ $? -ne 0 ]; then
+    echo "错误: 未找到python3,请先安装Python 3.8.3或更高版本"
+    exit 1
+fi
+
+# 检查pip
+echo "检查pip..."
+python3 -m pip --version
+if [ $? -ne 0 ]; then
+    echo "pip未安装,正在安装pip..."
+    python3 -m ensurepip --upgrade
+fi
+
+# 安装依赖
+echo "安装Python依赖包..."
+python3 -m pip install -r requirements.txt --user
+
+if [ $? -eq 0 ]; then
+    echo "依赖安装完成!"
+    echo ""
+    echo "运行数据库连接测试..."
+    python3 test_db_connection.py
+else
+    echo "依赖安装失败!"
+    exit 1
+fi
+

+ 217 - 0
parse_file.py

@@ -0,0 +1,217 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+解析振动数据文件
+从FDFS下载文件并解析数据
+"""
+
+import zlib
+import json
+import sys
+import os
+
+# 设置控制台输出编码(Windows需要,Linux默认UTF-8)
+if sys.platform == 'win32':
+    try:
+        os.system('chcp 65001 >nul 2>&1')
+    except:
+        pass
+else:
+    # Linux环境下设置UTF-8编码
+    import locale
+    try:
+        locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
+    except:
+        try:
+            locale.setlocale(locale.LC_ALL, 'C.UTF-8')
+        except:
+            pass
+
+def parse_file_data(file_name, use_fdfs=False, fdfs_client=None):
+    """
+    解析文件数据
+    
+    参数:
+        file_name: 文件路径(FDFS路径或本地路径)
+        use_fdfs: 是否使用FDFS客户端(如果为True,需要提供fdfs_client)
+        fdfs_client: FDFS客户端对象(可选)
+    
+    返回:
+        解析后的数据(字典或列表)
+    """
+    try:
+        # 1.1 从 FDFS 下载文件数据,先把文件名转成 UTF-8 编码的二进制串
+        if use_fdfs and fdfs_client:
+            # 使用FDFS客户端下载
+            data = fdfs_client.download_bytes(bytes(file_name, encoding="utf8"))
+        else:
+            # 如果没有FDFS客户端,尝试从本地文件系统读取
+            # 将FDFS路径转换为本地路径
+            if file_name.startswith('group1/M00/'):
+                local_path = file_name.replace('group1/M00/', '/home/soft/data/fdfs/storage/data/', 1)
+            elif file_name.startswith('group1/M00'):
+                local_path = file_name.replace('group1/M00', '/home/soft/data/fdfs/storage/data', 1)
+            else:
+                local_path = file_name
+            
+            # 从本地文件系统读取
+            with open(local_path, 'rb') as f:
+                data = f.read()
+        
+        # 1.2 检查下载的二进制数据开头是否是 {(JSON 格式的标志)
+        if data[0:1] == b'{':
+            # 如果是 JSON 二进制串,直接解码成字符串
+            data = data.decode()
+        else:
+            # 如果不是 JSON 开头,说明数据是压缩的,先解压
+            ss = zlib.decompress(data).decode()
+            # 修正格式:单引号改双引号(JSON 要求双引号)、nan 改成 0(避免 JSON 解析报错)
+            ss = ss.replace('\'', '"').replace("nan", "0")
+            # 把修正后的字符串解析成 JSON 格式的 Python 数据(字典/列表)
+            data = json.loads(ss)
+        
+        return data
+    except FileNotFoundError as e:
+        print(f"[错误] 文件未找到: {e}")
+        return None
+    except zlib.error as e:
+        print(f"[错误] 解压缩失败: {e}")
+        return None
+    except json.JSONDecodeError as e:
+        print(f"[错误] JSON解析失败: {e}")
+        return None
+    except Exception as e:
+        print(f"[错误] 解析文件时发生错误: {e}")
+        import traceback
+        traceback.print_exc()
+        return None
+
+def calculate_asymmetry(data):
+    """
+    计算不对称性
+    从data中提取raw_y数组,然后计算不对称性
+    返回 (abs_正, abs_负)
+    """
+    try:
+        # 如果data是字典,提取raw_y字段
+        if isinstance(data, dict):
+            if 'raw_y' not in data:
+                return None, None
+            values_list = data['raw_y']
+        elif isinstance(data, list):
+            # 如果是列表,直接使用(兼容旧代码)
+            values_list = data
+        else:
+            return None, None
+        
+        # 确保values_list是列表
+        if not isinstance(values_list, list):
+            return None, None
+        
+        # 转换为数值列表
+        values = []
+        for v in values_list:
+            try:
+                val = float(v)
+                values.append(val)
+            except (ValueError, TypeError):
+                continue
+        
+        if len(values) == 0:
+            return None, None
+        
+        # 分离正半轴和负半轴的值
+        positive_values = [v for v in values if v > 0]
+        negative_values = [abs(v) for v in values if v < 0]  # 负半轴取绝对值
+        
+        # 计算和
+        sum_positive = sum(positive_values)
+        sum_negative = sum(negative_values)
+        
+        # 计算不对称性
+        if sum_positive > 0:
+            abs_positive = abs((sum_positive - sum_negative) / sum_positive)
+        else:
+            abs_positive = None
+        
+        if sum_negative > 0:
+            abs_negative = abs((sum_positive - sum_negative) / sum_negative)
+        else:
+            abs_negative = None
+        
+        return abs_positive, abs_negative
+    except Exception as e:
+        print(f"[错误] 计算不对称性时发生错误: {e}")
+        return None, None
+
+def main():
+    """主函数"""
+    # 从命令行参数获取文件路径
+    if len(sys.argv) < 2:
+        print("[错误] 请提供文件路径作为参数")
+        print("使用方法: python parse_file.py <file_name>")
+        print("示例: python parse_file.py group1/M00/4C/AD/wKgUZWlQzpKAcUtmAA03b1suhI44618492")
+        sys.exit(1)
+    
+    file_name = sys.argv[1]
+    
+    print("=" * 60)
+    print("文件解析工具")
+    print("=" * 60)
+    print(f"文件路径: {file_name}")
+    print("-" * 60)
+    
+    # 解析文件
+    print("正在解析文件...")
+    data = parse_file_data(file_name, use_fdfs=False)
+    
+    if data is not None:
+        print("\n[成功] 文件解析成功!")
+        print("=" * 60)
+        print("解析后的数据:")
+        print("=" * 60)
+        
+        # 打印数据
+        # 如果是字典或列表,使用json.dumps格式化输出
+        if isinstance(data, (dict, list)):
+            print(json.dumps(data, indent=2, ensure_ascii=False))
+        else:
+            print(data)
+        
+        print("=" * 60)
+        
+        # 打印数据类型和基本信息
+        print(f"\n数据类型: {type(data).__name__}")
+        if isinstance(data, dict):
+            print(f"字典键数量: {len(data)}")
+            print(f"字典键: {list(data.keys())}")
+        elif isinstance(data, list):
+            print(f"列表长度: {len(data)}")
+            if len(data) > 0:
+                print(f"第一个元素类型: {type(data[0]).__name__}")
+                if isinstance(data[0], (dict, list)):
+                    print(f"第一个元素: {json.dumps(data[0], indent=2, ensure_ascii=False)[:200]}...")
+        
+        # 计算不对称性
+        print("\n" + "=" * 60)
+        print("不对称性计算:")
+        print("=" * 60)
+        abs_positive, abs_negative = calculate_asymmetry(data)
+        
+        if abs_positive is not None and abs_negative is not None:
+            print(f"abs正: {abs_positive:.10f}")
+            print(f"abs负: {abs_negative:.10f}")
+        else:
+            print("[警告] 无法计算不对称性(数据中可能缺少raw_y字段或raw_y为空)")
+            if isinstance(data, dict) and 'raw_y' in data:
+                raw_y = data['raw_y']
+                print(f"raw_y类型: {type(raw_y).__name__}")
+                if isinstance(raw_y, list):
+                    print(f"raw_y长度: {len(raw_y)}")
+    else:
+        print("\n[失败] 文件解析失败!")
+        sys.exit(1)
+
+if __name__ == "__main__":
+    main()
+

+ 457 - 0
query_device_filter.py

@@ -0,0 +1,457 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+查询td_d_log_result表,筛选符合条件的device_id和channel_no组合
+筛选条件:
+- task_id = 2
+- 对于每个device_id和channel_no的组合,取最新的10条记录(按channel_no的generate_time)
+- 如果这10个value值中有超过3个值大于20,则记录该(device_id, channel_no)组合
+"""
+
+import pymysql
+import sys
+import os
+import zlib
+import json
+from collections import defaultdict
+from datetime import datetime
+
+# 设置控制台输出编码(Windows需要,Linux默认UTF-8)
+if sys.platform == 'win32':
+    try:
+        os.system('chcp 65001 >nul 2>&1')
+    except:
+        pass
+else:
+    # Linux环境下设置UTF-8编码
+    import locale
+    try:
+        locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
+    except:
+        try:
+            locale.setlocale(locale.LC_ALL, 'C.UTF-8')
+        except:
+            pass
+
+def parse_file_data(file_name, use_fdfs=False, fdfs_client=None):
+    """
+    解析振动数据文件
+    
+    参数:
+        file_name: 文件路径(FDFS路径或本地路径)
+        use_fdfs: 是否使用FDFS客户端(如果为True,需要提供fdfs_client)
+        fdfs_client: FDFS客户端对象(可选)
+    
+    返回:
+        解析后的数据(字典或列表)
+    """
+    try:
+        # 1.1 从 FDFS 下载文件数据,先把文件名转成 UTF-8 编码的二进制串
+        if use_fdfs and fdfs_client:
+            # 使用FDFS客户端下载
+            data = fdfs_client.download_bytes(bytes(file_name, encoding="utf8"))
+        else:
+            # 如果没有FDFS客户端,尝试从本地文件系统读取
+            # 将FDFS路径转换为本地路径
+            if file_name.startswith('group1/M00/'):
+                local_path = file_name.replace('group1/M00/', '/home/soft/data/fdfs/storage/data/', 1)
+            elif file_name.startswith('group1/M00'):
+                local_path = file_name.replace('group1/M00', '/home/soft/data/fdfs/storage/data', 1)
+            else:
+                local_path = file_name
+            
+            # 从本地文件系统读取
+            with open(local_path, 'rb') as f:
+                data = f.read()
+        
+        # 1.2 检查下载的二进制数据开头是否是 {(JSON 格式的标志)
+        if data[0:1] == b'{':
+            # 如果是 JSON 二进制串,直接解码成字符串
+            data = data.decode()
+        else:
+            # 如果不是 JSON 开头,说明数据是压缩的,先解压
+            ss = zlib.decompress(data).decode()
+            # 修正格式:单引号改双引号(JSON 要求双引号)、nan 改成 0(避免 JSON 解析报错)
+            ss = ss.replace('\'', '"').replace("nan", "0")
+            # 把修正后的字符串解析成 JSON 格式的 Python 数据(字典/列表)
+            data = json.loads(ss)
+        
+        return data
+    except FileNotFoundError as e:
+        # 文件未找到,静默返回None
+        return None
+    except zlib.error as e:
+        print(f"      警告: 解压缩失败 {file_name}: {e}")
+        return None
+    except json.JSONDecodeError as e:
+        print(f"      警告: JSON解析失败 {file_name}: {e}")
+        return None
+    except Exception as e:
+        print(f"      警告: 解析文件失败 {file_name}: {e}")
+        return None
+
+def calculate_asymmetry(data):
+    """
+    计算不对称性
+    从data中提取raw_y数组,然后计算不对称性
+    返回 (abs_正, abs_负)
+    """
+    try:
+        # 如果data是字典,提取raw_y字段
+        if isinstance(data, dict):
+            if 'raw_y' not in data:
+                return None, None
+            values_list = data['raw_y']
+        elif isinstance(data, list):
+            # 如果是列表,直接使用(兼容旧代码)
+            values_list = data
+        else:
+            return None, None
+        
+        # 确保values_list是列表
+        if not isinstance(values_list, list):
+            return None, None
+        
+        # 转换为数值列表
+        values = []
+        for v in values_list:
+            try:
+                val = float(v)
+                values.append(val)
+            except (ValueError, TypeError):
+                continue
+        
+        if len(values) == 0:
+            return None, None
+        
+        # 分离正半轴和负半轴的值
+        positive_values = [v for v in values if v > 0]
+        negative_values = [abs(v) for v in values if v < 0]  # 负半轴取绝对值
+        
+        # 计算和
+        sum_positive = sum(positive_values)
+        sum_negative = sum(negative_values)
+        
+        # 计算不对称性
+        if sum_positive > 0:
+            abs_positive = abs((sum_positive - sum_negative) / sum_positive)
+        else:
+            abs_positive = None
+        
+        if sum_negative > 0:
+            abs_negative = abs((sum_positive - sum_negative) / sum_negative)
+        else:
+            abs_negative = None
+        
+        return abs_positive, abs_negative
+    except Exception as e:
+        return None, None
+
+def query_filtered_devices(fdfs_client=None):
+    """
+    查询符合条件的device_id和channel_no组合
+    
+    参数:
+        fdfs_client: FDFS客户端对象(可选,用于下载文件)
+    """
+    # 数据库连接配置
+    db_config = {
+        'host': '127.0.0.1',
+        'port': 3306,
+        'user': 'prod',
+        'password': 'hmdmxjIvfIjIoflL',
+        'database': 'iot',
+        'charset': 'utf8mb4',
+        'connect_timeout': 10
+    }
+    
+    connection = None
+    try:
+        print("正在连接到数据库...")
+        connection = pymysql.connect(**db_config)
+        print("[成功] 数据库连接成功!\n")
+        
+        with connection.cursor() as cursor:
+            # 查询所有唯一的device_id和channel_no组合,且task_id=2
+            print("查询所有device_id和channel_no的组合...")
+            query_unique = """
+                SELECT DISTINCT device_id, channel_no 
+                FROM td_d_log_result 
+                WHERE task_id = '2'
+            """
+            cursor.execute(query_unique)
+            device_channel_pairs = cursor.fetchall()
+            print(f"找到 {len(device_channel_pairs)} 个device_id和channel_no的组合\n")
+            
+            # 存储符合条件的(device_id, channel_no)组合
+            filtered_channel_list = []
+            
+            # 对每个device_id和channel_no组合进行处理
+            for idx, (device_id, channel_no) in enumerate(device_channel_pairs, 1):
+                # 查询该组合的最新10条记录(按generate_time降序,按channel_no)
+                query_latest = """
+                    SELECT value, generate_time
+                    FROM td_d_log_result
+                    WHERE device_id = %s 
+                      AND channel_no = %s 
+                      AND task_id = '2'
+                    ORDER BY generate_time DESC
+                    LIMIT 10
+                """
+                cursor.execute(query_latest, (device_id, channel_no))
+                records = cursor.fetchall()
+                
+                if len(records) < 10:
+                    # 如果记录数少于10条,跳过
+                    continue
+                
+                # 统计value值大于20的个数
+                count_over_20 = 0
+                values = []
+                
+                for value_str, generate_time in records:
+                    try:
+                        # value字段是varchar类型,需要转换为float
+                        value = float(value_str) if value_str else 0
+                        values.append(value)
+                        if value > 20:
+                            count_over_20 += 1
+                    except (ValueError, TypeError):
+                        # 如果转换失败,跳过该值
+                        continue
+                
+                # 如果超过3个值大于20,记录该(device_id, channel_no)组合
+                if count_over_20 > 3:
+                    filtered_channel_list.append((device_id, channel_no))
+                    print(f"[{idx}] device_id: {device_id}, channel_no: {channel_no}, "
+                          f"超过20的值数量: {count_over_20}/10, 最新10个值: {values}")
+            
+            # 查询device_name
+            print("\n查询设备名称...")
+            device_info_map = {}
+            if filtered_channel_list:
+                # 获取所有唯一的device_id
+                unique_device_ids = list(set([device_id for device_id, channel_no in filtered_channel_list]))
+                
+                # 批量查询device_name
+                placeholders = ','.join(['%s'] * len(unique_device_ids))
+                query_device_name = f"""
+                    SELECT device_id, device_name 
+                    FROM td_d_device_info 
+                    WHERE device_id IN ({placeholders})
+                """
+                cursor.execute(query_device_name, tuple(unique_device_ids))
+                device_info_results = cursor.fetchall()
+                
+                # 构建device_id到device_name的映射
+                for device_id, device_name in device_info_results:
+                    device_info_map[device_id] = device_name if device_name else '(未设置设备名称)'
+            
+            # 查询channel_name
+            print("查询通道名称...")
+            channel_info_map = {}
+            if filtered_channel_list:
+                # 批量查询channel_name(使用device_id和channel_no组合)
+                query_channel_name = """
+                    SELECT device_id, channel_no, channel_name 
+                    FROM td_d_device_channel 
+                    WHERE (device_id, channel_no) IN (
+                """
+                # 构建IN子句的占位符
+                placeholders = ','.join(['(%s, %s)'] * len(filtered_channel_list))
+                query_channel_name += placeholders + ")"
+                
+                # 准备参数:将(device_id, channel_no)元组展开
+                params = []
+                for device_id, channel_no in filtered_channel_list:
+                    params.extend([device_id, int(channel_no)])  # channel_no需要转换为int
+                
+                cursor.execute(query_channel_name, tuple(params))
+                channel_info_results = cursor.fetchall()
+                
+                # 构建(device_id, channel_no)到channel_name的映射
+                for device_id, channel_no, channel_name in channel_info_results:
+                    channel_info_map[(device_id, str(channel_no))] = channel_name if channel_name else '(未设置通道名称)'
+            
+            # 查询每个(device_id, channel_no)组合最近10条的file_name
+            print("查询每个channel_no最近10条文件记录...")
+            channel_file_map = {}
+            if filtered_channel_list:
+                for device_id, channel_no in filtered_channel_list:
+                    # 查询该(device_id, channel_no)组合最近10条记录的file_name(按generate_time降序,task_id=2)
+                    # 同时关联查询td_d_log_result表的generate_time
+                    query_file_name = """
+                        SELECT lf.file_name, lf.generate_time as file_generate_time, lr.generate_time as result_generate_time
+                        FROM td_d_log_file lf
+                        LEFT JOIN td_d_log_result lr ON (
+                            lf.device_id = lr.device_id 
+                            AND lf.channel_no = lr.channel_no 
+                            AND lf.task_id = lr.task_id 
+                            AND lf.generate_time = lr.generate_time
+                        )
+                        WHERE lf.device_id = %s 
+                          AND lf.channel_no = %s
+                          AND lf.task_id = '2'
+                        ORDER BY lf.generate_time DESC
+                        LIMIT 10
+                    """
+                    cursor.execute(query_file_name, (device_id, channel_no))
+                    file_results = cursor.fetchall()
+                    
+                    # 提取file_name列表,同时解析文件计算不对称性(使用FDFS路径)
+                    file_info_list = []
+                    for file_name, file_generate_time, result_generate_time in file_results:
+                        if file_name:
+                            # 解析文件并计算不对称性(使用原始FDFS路径,如果提供了fdfs_client则使用FDFS,否则从本地读取)
+                            data = parse_file_data(file_name, use_fdfs=(fdfs_client is not None), fdfs_client=fdfs_client)
+                            abs_positive, abs_negative = calculate_asymmetry(data) if data is not None else (None, None)
+                            
+                            file_info_list.append({
+                                'file_name': file_name,  # 保持原始FDFS路径
+                                'file_generate_time': file_generate_time,
+                                'result_generate_time': result_generate_time,
+                                'abs_positive': abs_positive,
+                                'abs_negative': abs_negative
+                            })
+                    channel_file_map[(device_id, channel_no)] = file_info_list
+            
+            # 生成输出文件(包含时间戳)
+            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
+            output_filename = f"query_result_{timestamp}.txt"
+            output_filepath = os.path.join(os.getcwd(), output_filename)
+            
+            # 打印所有符合条件的device_id、device_name、channel_no、channel_name和最近10条file_name
+            print("\n" + "=" * 60)
+            print("符合条件的device_id和channel_no组合列表:")
+            print("=" * 60)
+            
+            # 准备输出内容(同时用于打印和写入文件)
+            output_lines = []
+            output_lines.append("=" * 60)
+            output_lines.append("符合条件的device_id和channel_no组合列表:")
+            output_lines.append("=" * 60)
+            output_lines.append("")
+            
+            if filtered_channel_list:
+                # 按device_id和channel_no排序后打印
+                sorted_channels = sorted(filtered_channel_list, key=lambda x: (x[0], int(x[1]) if x[1].isdigit() else x[1]))
+                displayed_count = 0  # 记录实际显示的记录数
+                for device_id, channel_no in sorted_channels:
+                    device_name = device_info_map.get(device_id, '(未找到设备名称)')
+                    channel_name = channel_info_map.get((device_id, channel_no), '(未找到通道名称)')
+                    
+                    # 过滤掉"未找到通道名称"的记录
+                    if channel_name == '(未找到通道名称)':
+                        continue
+                    
+                    displayed_count += 1  # 增加显示计数
+                    file_names = channel_file_map.get((device_id, channel_no), [])
+                    
+                    device_info_str = f"\n设备ID: {device_id}"
+                    device_name_str = f"设备名称: {device_name}"
+                    channel_no_str = f"通道号: {channel_no}"
+                    channel_name_str = f"通道名称: {channel_name}"
+                    file_count_str = f"最近10条文件记录({len(file_names)}条):"
+                    
+                    print(device_info_str)
+                    print(device_name_str)
+                    print(channel_no_str)
+                    print(channel_name_str)
+                    print(file_count_str)
+                    
+                    output_lines.append(device_info_str)
+                    output_lines.append(device_name_str)
+                    output_lines.append(channel_no_str)
+                    output_lines.append(channel_name_str)
+                    output_lines.append(file_count_str)
+                    
+                    if file_names:
+                        for idx, file_info in enumerate(file_names, 1):
+                            file_name = file_info['file_name']
+                            file_generate_time = file_info.get('file_generate_time')
+                            result_generate_time = file_info.get('result_generate_time')
+                            abs_positive = file_info.get('abs_positive')
+                            abs_negative = file_info.get('abs_negative')
+                            
+                            # 格式化输出
+                            file_time_str = str(file_generate_time) if file_generate_time else "N/A"
+                            result_time_str = str(result_generate_time) if result_generate_time else "N/A"
+                            abs_positive_str = f"{abs_positive:.6f}" if abs_positive is not None else "N/A"
+                            abs_negative_str = f"{abs_negative:.6f}" if abs_negative is not None else "N/A"
+                            
+                            file_line = f"  {idx}. {file_name}"
+                            time_line = f"      generate_time(td_d_log_result): {result_time_str}"
+                            abs_line = f"      abs正: {abs_positive_str}, abs负: {abs_negative_str}"
+                            
+                            print(file_line)
+                            print(time_line)
+                            print(abs_line)
+                            
+                            output_lines.append(file_line)
+                            output_lines.append(time_line)
+                            output_lines.append(abs_line)
+                    else:
+                        no_file_str = "  (无文件记录)"
+                        print(no_file_str)
+                        output_lines.append(no_file_str)
+                    
+                    separator = "-" * 60
+                    print(separator)
+                    output_lines.append(separator)
+                
+                total_str = f"\n总计: {displayed_count} 个(device_id, channel_no)组合(已过滤掉未找到通道名称的记录)"
+                print(total_str)
+                output_lines.append(total_str)
+            else:
+                no_result_str = "未找到符合条件的(device_id, channel_no)组合"
+                print(no_result_str)
+                output_lines.append(no_result_str)
+            
+            # 将结果写入文件
+            try:
+                with open(output_filepath, 'w', encoding='utf-8') as f:
+                    f.write('\n'.join(output_lines))
+                    f.write('\n')
+                print(f"\n[成功] 查询结果已保存到文件: {output_filepath}")
+            except Exception as e:
+                print(f"\n[警告] 保存结果到文件失败: {e}")
+            
+            return filtered_channel_list
+        
+    except pymysql.Error as e:
+        error_code, error_msg = e.args
+        print("[失败] 数据库操作失败!")
+        print(f"[失败] 错误代码: {error_code}")
+        print(f"[失败] 错误信息: {error_msg}")
+        return None
+        
+    except Exception as e:
+        print("[失败] 发生未知错误!")
+        print(f"[失败] 错误信息: {e}")
+        import traceback
+        traceback.print_exc()
+        return None
+        
+    finally:
+        # 关闭数据库连接
+        if connection:
+            connection.close()
+            print("\n[成功] 数据库连接已关闭")
+
+if __name__ == "__main__":
+    print("=" * 60)
+    print("设备和通道筛选工具")
+    print("=" * 60)
+    print("筛选条件:")
+    print("- task_id = 2")
+    print("- 每个device_id下channel_no的最新10条记录(按channel_no的generate_time)")
+    print("- 这10个value值中有超过3个值大于20")
+    print("=" * 60 + "\n")
+    
+    result = query_filtered_devices()
+    
+    if result is not None:
+        sys.exit(0)
+    else:
+        sys.exit(1)
+

+ 2 - 0
requirements.txt

@@ -0,0 +1,2 @@
+pymysql==1.0.2
+

+ 0 - 0
景兴故障测点筛选.txt