Skip to content

Commit 7484592

Browse files
committed
Update from imCloud Repo
1 parent e560230 commit 7484592

File tree

8 files changed

+472
-354
lines changed

8 files changed

+472
-354
lines changed

API/Master.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
[SaveUnit]
2-
second = 600
2+
second = 60
33
sql_upload_interval = 5

API/sql.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[SQLServer]
2-
enabled = true
2+
enabled = false
33
host = 192.168.9.13
44
port = 3306
55
user = raspberrypi

README.md

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -717,14 +717,21 @@ function updateChart() {
717717
- `_ensure_connected()` - 確保連線存在(自動重連)
718718
- `_read_chip_id()` - 讀取晶片 ID(初始化時)
719719
- `_set_sample_rate()` - 設定取樣率(初始化時)
720-
- `_read_fifo_length()` - 讀取 FIFO 長度(寄存器 0x02)
721-
- `_read_raw_block(data_len)` - 讀取原始資料(寄存器 0x03)
722-
- `_convert_raw_to_float_samples(raw_block)` - 轉換為浮點數(確保 XYZ 不錯位)
720+
- `_read_registers_with_header()` - 讀取寄存器(包含 Header)
721+
- `_read_normal_data()` - Normal Mode 讀取(Address 0x02)
722+
- `_read_bulk_data()` - Bulk Mode 讀取(Address 0x15)
723+
- `_get_buffer_status()` - 讀取緩衝區狀態
724+
- `_convert_raw_to_float_samples()` - 轉換為浮點數(確保 XYZ 不錯位)
723725
- `_read_loop()` - 主要讀取迴圈(背景執行緒)
724726

727+
**讀取模式:**
728+
- **Normal Mode**:當緩衝區資料量 ≤ 123 時使用,從 Address 0x02 讀取
729+
- **Bulk Mode**:當緩衝區資料量 > 123 時使用,從 Address 0x15 讀取,最多讀取 9 個樣本
730+
- 自動根據緩衝區狀態切換模式,優化讀取效率
731+
725732
**設計原則:**
726733
- 每次讀取只處理完整的 XYZ 三軸組,避免通道錯位
727-
- 不跨批拼接 raw data,確保資料完整性
734+
- FIFO buffer size(0x02) 連同資料一起讀出,確保一致性
728735
- 自動重連機制,確保連線穩定性
729736
- 模組化設計,方便未來擴展和維護
730737

@@ -734,20 +741,24 @@ function updateChart() {
734741
ProWaveDAQ 設備
735742
↓ (Modbus RTU)
736743
ProWaveDAQ._read_loop() [背景執行緒]
737-
├─ _read_fifo_length() # 讀取 FIFO 長度(寄存器 0x02)
738-
├─ _read_raw_block() # 讀取原始資料(寄存器 0x03)
744+
├─ _get_buffer_status() # 讀取緩衝區狀態(寄存器 0x02)
745+
├─ 模式判斷
746+
│ ├─ buffer_count ≤ 123 → Normal Mode
747+
│ │ └─ _read_normal_data() # 從 Address 0x02 讀取
748+
│ └─ buffer_count > 123 → Bulk Mode
749+
│ └─ _read_bulk_data() # 從 Address 0x15 讀取(最多 9 個樣本)
750+
├─ _read_registers_with_header() # 讀取 Header + 資料
739751
└─ _convert_raw_to_float_samples() # 轉換為浮點數(確保 XYZ 不錯位)
740752
↓ (放入佇列)
741-
data_queue (queue.Queue, 最大 1000 筆)
753+
data_queue (queue.Queue, 最大 10000 筆)
742754
743755
collection_loop() [背景執行緒]
744756
745757
├─→ update_realtime_data()
746758
│ │
747759
│ ▼
748-
│ realtime_data (List[float], 最多 10000 點)
760+
│ realtime_data (List[float], 無限制)
749761
│ │ - 智慧緩衝區:僅在有活躍連線時更新
750-
│ │ - 記憶體管理:超過 10000 點時只保留最近 10000 點
751762
│ │
752763
│ ▼
753764
│ Flask /data API (HTTP GET, 每 200ms)
@@ -881,6 +892,29 @@ collection_loop() [背景執行緒]
881892

882893
## 更新日誌
883894

895+
### Version 3.0.0
896+
- **重大更新**:重構資料讀取邏輯
897+
- 實現 Normal Mode 和 Bulk Mode 自動切換機制
898+
- 根據緩衝區狀態(buffer_count)動態選擇讀取模式
899+
- Normal Mode:當 buffer_count ≤ 123 時,從 Address 0x02 讀取
900+
- Bulk Mode:當 buffer_count > 123 時,從 Address 0x15 讀取(最多 9 個樣本)
901+
- FIFO buffer size(0x02) 連同資料一起讀出,確保資料一致性
902+
- **改進**:讀取效率優化
903+
- 一次讀取 Header 和資料,減少 Modbus 通訊次數
904+
- 使用 `_read_registers_with_header()` 方法統一處理讀取邏輯
905+
- 參考 LabView 轉換版本(G.py)的實現方式
906+
- **改進**:程式碼清理
907+
- 移除所有冗餘註解和步驟標記
908+
- 簡化程式碼結構,提升可讀性
909+
- 移除不再使用的舊方法(`_read_fifo_length``_read_raw_block` 等)
910+
- **改進**:即時資料顯示
911+
- 移除資料點數限制(原本限制 100000 個資料點)
912+
- 現在會保留並顯示所有資料
913+
- **改進**:文件更新
914+
- 更新 README.md 和 程式運作說明.md
915+
- 詳細說明 Normal Mode 和 Bulk Mode 的運作方式
916+
- 更新資料流程圖和架構說明
917+
884918
### Version 2.3.0
885919
- **新增**:支援啟動時指定 port
886920
- 可透過命令行參數 `--port``-p` 指定 Flask 伺服器埠號
@@ -972,6 +1006,6 @@ collection_loop() [背景執行緒]
9721006

9731007
---
9741008

975-
**最後更新**2025年11月24日
1009+
**最後更新**2025年12月03日
9761010
**作者**:王建葦
977-
**當前版本**2.2.0
1011+
**當前版本**3.0.0

deploy.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,8 @@ echo ""
2424
echo -e "${YELLOW}[1/6] Checking and installing Python packages...${NC}"
2525

2626
# Check if Debian/Ubuntu system
27-
apt-get update && apt-get upgrade -y
28-
apt-get install -y python3 python3-pip python3-venv
27+
sudo apt update && sudo apt upgrade -y
28+
sudo apt install -y python3 python3-pip python3-venv
2929

3030
PYTHON_VERSION=$(python3 --version | awk '{print $2}')
3131
echo -e "${GREEN}Python version: $PYTHON_VERSION${NC}"
@@ -35,7 +35,7 @@ echo ""
3535
echo -e "${YELLOW}[2/6] Setting up Python virtual environment...${NC}"
3636
if [ -d "venv" ]; then
3737
echo "Removing existing virtual environment..."
38-
rm -rf venv
38+
sudo rm -rf venv
3939
echo -e "${GREEN} Old virtual environment removed${NC}"
4040
fi
4141
echo "Creating virtual environment..."

src/G.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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

Comments
 (0)