1+ # 這個是G哥給的正確讀取程式碼,從 LabView 轉換過來的
2+
3+ import time
4+ import threading
5+ import queue
6+ from pymodbus .client import ModbusSerialClient as ModbusClient
7+ from pymodbus .constants import Endian
8+ from pymodbus .payload import BinaryPayloadDecoder
9+
10+ # 全域參數定義
11+ SLAVE_ID = 1
12+ CHIP_ID_ADDRESS = 0x1B
13+ SAMPLE_RATE_ADDRESS = 0x01
14+ BUFFER_STATUS_ADDRESS = 0x02 # Normal Mode 數據和長度讀取的起始地址
15+ BULK_MODE_ADDRESS = 0x15 # 21 (x15), Bulk Mode 數據的起始地址
16+ MAX_BULK_SIZE = 9 # 9, 建議的 Bulk 傳輸區塊大小
17+ BULK_TRIGGER_SIZE = 123 # Normal Mode 的最大讀取上限/ Bulk Mode 的切換門檻
18+ DEFAULT_BAUDRATE = 3000000 # 預設鮑率
19+ TARGET_SAMPLE_RATE = 7812 # 取樣率 7812 Hz
20+
21+ # 執行緒安全的資料佇列,用於後端與前端的通訊
22+ DATA_QUEUE = queue .Queue ()
23+
24+ class VibSensorDriver :
25+ """
26+ PWRVT 三軸振動即時採集程式的 Modbus 通訊驅動。
27+ """
28+ def __init__ (self , port , baudrate = DEFAULT_BAUDRATE , timeout = 0.5 ):
29+ self .client = ModbusClient (
30+ method = 'rtu' ,
31+ port = port ,
32+ baudrate = baudrate ,
33+ timeout = timeout
34+ )
35+ self .buffer_count = 0 # 用於追蹤緩衝區剩餘資料量
36+
37+ # 啟動連線
38+ if not self .client .connect ():
39+ raise ConnectionError (f"無法連線到 Modbus 裝置於 { port } " )
40+
41+ self .client .framer .skip_encode_mobile = True
42+ self .client .framer .decode_buffer_size = 2048
43+ print (f"Modbus 連線成功於 { port } @ { baudrate } bps" )
44+
45+ def read_chip_id (self ):
46+ """
47+ 讀取晶片/UCID (FC=03, Address 0x1B)
48+ """
49+ rr = self .client .read_holding_registers (address = CHIP_ID_ADDRESS , count = 2 , slave = SLAVE_ID )
50+ if rr .isError ():
51+ return None
52+ decoder = BinaryPayloadDecoder .fromRegisters (rr .registers , byteorder = Endian .Big , wordorder = Endian .Big )
53+ return decoder .decode_32bit_int ()
54+
55+ def set_sample_rate (self , sample_rate_value : int ):
56+ """
57+ 設定採樣率 (FC=06, Address 0x01)
58+ """
59+ rr = self .client .write_register (address = SAMPLE_RATE_ADDRESS , value = sample_rate_value , slave = SLAVE_ID )
60+ return not rr .isError ()
61+
62+ def _read_registers_with_header (self , address , count , mode_name ):
63+ """
64+ 執行 Modbus 讀取 (FC=04),並處理標頭(Header)
65+ """
66+ # 讀取點數是 N + 1 (第一個暫存器是 Header)
67+ read_count = count + 1
68+
69+ # 假設所有 Raw Data 讀取均使用 Read Input Registers (FC=04)
70+ rr = self .client .read_input_registers (address = address , count = read_count , slave = SLAVE_ID )
71+
72+ if rr .isError () or len (rr .registers ) != read_count :
73+ print (f"錯誤或資料長度不符 in { mode_name } Read: { rr } " )
74+ return [], 0
75+
76+ raw_data = rr .registers
77+
78+ # 刪除 Header
79+ payload_data = raw_data [1 :]
80+
81+ # 緩衝區剩餘數
82+ remaining_samples = raw_data [0 ]
83+
84+ return payload_data , remaining_samples
85+
86+ def read_normal_data (self , samples_to_read : int ):
87+ """
88+ Normal Mode 讀取 (FC=04, Address 0x02)
89+ """
90+ return self ._read_registers_with_header (
91+ address = BUFFER_STATUS_ADDRESS ,
92+ count = samples_to_read ,
93+ mode_name = "Normal Mode"
94+ )
95+
96+ def read_bulk_data (self , samples_to_read : int ):
97+ """
98+ Bulk Mode 讀取 (FC=04, Address 0x15)
99+ """
100+ # 實際讀取樣本數必須限制在 MAX_BULK_SIZE 內 (9)
101+ bulk_count = min (samples_to_read , MAX_BULK_SIZE )
102+
103+ return self ._read_registers_with_header (
104+ address = BULK_MODE_ADDRESS ,
105+ count = bulk_count ,
106+ mode_name = "Bulk Mode"
107+ )
108+
109+ def acquisition_loop (self ):
110+ """
111+ 持續運行並將資料放入佇列(主採集迴圈 / 生產者)
112+ """
113+ print ("--- 啟動數據採集迴圈 (Producer Thread) ---" )
114+
115+ while True :
116+ try :
117+ # 檢視有多少資料
118+ if self .buffer_count == 0 :
119+ self .buffer_count = self .get_buffer_status () # 呼叫 FC=04, 讀取 0x02 (長度 1)
120+ if self .buffer_count == 0 :
121+ time .sleep (0.01 ) # 緩衝區為 0,暫待緩衝區補值
122+ continue
123+
124+ # 判斷並執行讀取模式
125+ if self .buffer_count <= BULK_TRIGGER_SIZE :
126+ # Normal Mode
127+ samples_to_read = self .buffer_count
128+
129+ # Normal Mode 最大讀取長度必須限制在 samples_to_read <= 123
130+ final_read_count = min (samples_to_read , BULK_TRIGGER_SIZE )
131+
132+ collected_data , remaining = self .read_normal_data (final_read_count )
133+
134+ else :
135+ # Bulk Mode
136+ samples_to_read = self .buffer_count
137+
138+ # Bulk Read 必須限制在 MAX_BULK_SIZE (9)
139+ final_read_count = min (samples_to_read , MAX_BULK_SIZE )
140+
141+ collected_data , remaining = self .read_bulk_data (final_read_count )
142+
143+
144+ # 資料解編與輸出
145+ if collected_data and len (collected_data ) % 3 == 0 :
146+
147+ # X 軸數據:索引 0, 3, 6, ...
148+ x_data = collected_data [0 ::3 ]
149+ # Y 軸數據:索引 1, 4, 7, ...
150+ y_data = collected_data [1 ::3 ]
151+ # Z 軸數據:索引 2, 5, 8, ...
152+ z_data = collected_data [2 ::3 ]
153+
154+ # 將解編後的數據放入佇列供前端使用
155+ DATA_QUEUE .put ({"x" : x_data , "y" : y_data , "z" : z_data })
156+
157+ # 更新緩衝區狀態
158+ self .buffer_count = remaining
159+
160+ elif collected_data :
161+ # 讀到的資料點數非 3 的倍數,視為錯誤,下次重新詢問
162+ print ("Warning: Collected data length is not a multiple of 3. Resetting buffer count." )
163+ self .buffer_count = 0
164+
165+ else :
166+ # 讀取失敗,下次重新詢問
167+ self .buffer_count = 0
168+
169+ time .sleep (0.0001 )
170+
171+ except Exception as e :
172+ print (f"Acquisition Loop Error: { e } " )
173+ self .buffer_count = 0 # 發生錯誤時重置狀態
174+ time .sleep (1 )
175+
176+ def get_buffer_status (self ):
177+ """
178+ 讀取緩衝區樣本數 (FC=04, Address 0x02)
179+ """
180+ rr = self .client .read_input_registers (address = BUFFER_STATUS_ADDRESS , count = 1 , slave = SLAVE_ID )
181+ return rr .registers [0 ] if not rr .isError () else 0
182+
183+ def close (self ):
184+ """關閉連線"""
185+ self .client .close ()
186+
187+
188+ # 啟動函式
189+ def start_driver_thread (com_port , baud_rate ):
190+ """
191+ 初始化驅動並啟動生產者執行緒
192+ """
193+ try :
194+ driver = VibSensorDriver (port = com_port , baudrate = baud_rate )
195+
196+ # 確保設定採樣率
197+ if not driver .set_sample_rate (TARGET_SAMPLE_RATE ):
198+ print ("FATAL ERROR: 無法設定採樣率,請檢查設備是否支援或連線是否正常。" )
199+ return None
200+
201+ # 啟動執行緒
202+ t = threading .Thread (target = driver .acquisition_loop )
203+ t .daemon = True
204+ t .start ()
205+
206+ return driver
207+ except ConnectionError as e :
208+ print (f"無法啟動驅動程序:{ e } " )
209+ return None
0 commit comments