tiến hành alarm hệ thống được
Mình sẽ giải nén bản mới (z358) và xem chỗ lưu cấu hình dự án, để thêm cài đặt vào tab Trend/Alarm.
Mình đã thêm mục ⚙ Cài đặt lưu trữ vào tab Trend (cho History) và tab Alarm (cho Alarm). Chỉ Admin được sửa, user khác chỉ xem. Cài đặt lưu trong project.json và áp dụng ngay, không cần khởi động lại.
Mình chưa build được cả dự án vì máy này không tải được các gói NuGet. Phần đã kiểm tra:
- Biên dịch riêng
StorageCfg.cs,History.cs,AlarmStore.csvàStorageSettings.razor(Sqlite và engine dùng bản giả): sạch, 0 lỗi. - Các câu SQL mới (nén, xoá từng đoạn, thu nhỏ file) chạy thử bằng sqlite3 trên 72.000 dòng mẫu: nén đúng 1 điểm/phút/tag, file nhỏ lại sau khi xoá.
- Các sửa trong
ScadaEngine.cs,ScadaEnginePerf.cs,Models.cs,Home.razorchỉ đọc lại bằng mắt, chưa biên dịch. Bạn build rồi thử giúp mình.
Dung lượng và thời gian giữ (cả History và Alarm)
- Dung lượng tối đa: chọn nhanh 0.5, 1, 2, 3, 5, 10, 15, 20, 30 GB, hoặc nhập tay từ 0.1 đến 1000 GB. Hạ dung lượng xuống thì database tự co lại ngay, chạy nền.
- Giữ tối đa N ngày:
0nghĩa là không giới hạn theo ngày, chỉ xoá khi đầy dung lượng. Alarm đang ACTIVE không bị xoá. - Thanh trạng thái: hiện dung lượng đang dùng so với giới hạn, ổ đĩa còn trống, và ước tính với giới hạn này giữ được khoảng bao nhiêu ngày.
Lưu History thông minh
- 4 nút cài nhanh:
- Chi tiết (1 điểm/5 giây, lưu cả khi không đổi).
- Cân bằng (mặc định như cũ).
- Tiết kiệm (bỏ qua dao động dưới 0,5%, dữ liệu trên 7 ngày nén còn 1 điểm/phút).
- Nhẹ máy (dùng cho dự án lớn).
- Chỉnh từng mục:
- Chu kỳ lưu.
- Chỉ lưu khi đổi.
- Bỏ qua dao động dưới x% (mới).
- Giữ nhịp.
- Nén dữ liệu cũ (mới).
- Ghi xuống ổ đĩa mỗi 2–60 giây (mới).
- Ngừng ghi khi ổ đĩa trống dưới X GB (mới).
Alarm
- Chống nhảy alarm: sau khi một alarm vừa hết báo, trong N giây luật đó không báo lại. Giá trị dao động quanh ngưỡng sẽ không sinh hàng nghìn dòng. Mặc định là tắt.
Tăng tốc máy
Dọn dẹp, nén và thu nhỏ file đều chạy ở thread nền, xoá từng đoạn nhỏ. Trước đây việc xoá chạy ngay trong vòng lặp engine, với database hàng GB sẽ làm đứng và khoá ghi lâu.
Điểm cần lưu ý
- Mặc định giữ nguyên: dự án cũ chưa có mục này vẫn là 1 GB, history 30 ngày, alarm không giới hạn ngày. Chỉ thêm một mặc định mới: ngừng ghi history khi ổ đĩa trống dưới 1 GB. Đặt
0để tắt. - Nén dữ liệu cũ không hoàn lại được. Mặc định đang tắt.
- Ghi xuống ổ đĩa lâu hơn: nếu đặt 30–60 giây thì mất điện đột ngột có thể mất tối đa chừng đó giây dữ liệu chưa ghi.
- Khởi động không còn kiểm tra dung lượng ngay. Trước đó, lúc khởi động cài đặt chưa nạp xong nên sẽ xoá nhầm theo mặc định 1 GB. Giờ việc dọn chạy nền ở lần ghi đầu tiên.
- Trend 1 phút đến 8 giờ không đổi: đoạn 8 giờ gần nhất trong RAM vẫn lấy mẫu 1 giây. Các cài đặt trên chỉ ảnh hưởng database lâu dài.
- Tab Trend/Alarm: mình đặt History ở tab Trend và Alarm ở tab Alarm. Nếu bạn muốn gộp cả hai vào một chỗ thì báo mình sửa.
Trong luu_tru_patch_z358.zip có 10 file đã sửa hoặc thêm, đúng đường dẫn như trong dự án, chép đè là xong. Mình thêm 2 file mới: StorageCfg.cs (cấu hình + tiện ích dùng chung) và Components/StorageSettings.razor (giao diện). luu_tru.diff để bạn đối chiếu. HUONG_DAN_LUU_TRU.md trong zip là hướng dẫn chi tiết bằng tiếng Việt.
# Cài đặt lưu trữ History (Trend) và Alarm
Vào tab **Trend** → mục **⚙ Cài đặt lưu trữ History (Trend)**, hoặc tab **Alarm** → **⚙ Cài đặt lưu trữ Alarm** (bấm vào tiêu đề để mở).
Chỉ **Admin** được sửa; user khác chỉ xem. Giá trị lưu trong `project.json` (mục `Storage`), **áp dụng ngay, không cần khởi động lại**.
Dự án cũ chưa có mục này tự dùng giá trị mặc định = hành vi như trước (1 GB, history giữ 30 ngày, alarm không giới hạn ngày).
## Dung lượng và thời gian giữ (cả History và Alarm)
| Cài đặt | Ý nghĩa |
|---|---|
| Dung lượng tối đa | Chọn nhanh 0.5 / 1 / 2 / 3 / 5 / 10 / 15 / 20 / 30 GB hoặc **nhập tay** (0.1 – 1000 GB). Vượt thì tự xoá 10% khoảng thời gian cũ nhất, lặp tới khi còn ≤ 90%, rồi ghi tiếp. Hạ dung lượng xuống thì database được thu nhỏ ngay (chạy nền). |
| Giữ tối đa N ngày | Quá N ngày thì xoá (mỗi ngày 1 lần). `0` = không giới hạn theo ngày, chỉ theo dung lượng. Alarm đang ACTIVE không bị xoá. |
Thanh trạng thái hiện: dung lượng đang dùng / giới hạn, kích thước file thật (gồm `-wal`), khoảng thời gian dữ liệu, ổ đĩa còn trống,
và ước tính "với giới hạn này giữ được khoảng bao nhiêu ngày".
## Chế độ lưu History thông minh
Bốn nút cài nhanh (không đổi dung lượng / số ngày giữ):
| Chế độ | Chu kỳ | Chỉ lưu khi đổi | Bỏ qua dao động | Giữ nhịp | Nén dữ liệu cũ | Ghi xuống đĩa |
|---|---|---|---|---|---|---|
| 🔬 Chi tiết | 5 s | tắt | 0 | 60 s | tắt | 5 s |
| ⚖ Cân bằng (mặc định) | 10 s | bật | 0 | 60 s | tắt | 5 s |
| 🧠 Tiết kiệm | 10 s | bật | 0,5 % | 120 s | > 7 ngày → 1 điểm/phút | 10 s |
| 🚀 Nhẹ máy (dự án lớn) | 30 s | bật | 1 % | 300 s | > 3 ngày → 1 điểm/5 phút | 15 s |
Hoặc chỉnh từng mục:
- **Chu kỳ lưu**: mỗi tag tối đa 1 điểm / N giây trong database.
- **Chỉ lưu khi giá trị thay đổi** + **Bỏ qua dao động < x %** (so với điểm đã ghi gần nhất) + **Giữ nhịp** (đứng yên vẫn ghi 1 điểm / N giây để đồ thị liền nét).
- **Nén dữ liệu cũ**: dữ liệu cũ hơn N ngày chỉ giữ 1 điểm / 30 s – 10 phút cho mỗi tag. Chạy nền từng đoạn 10 phút; **không hoàn lại được**; phần mới vẫn chi tiết.
- **Ghi xuống ổ đĩa mỗi N giây**: gom lô, càng lớn càng nhẹ máy; đổi lại mất điện đột ngột có thể mất tối đa N giây dữ liệu chưa ghi.
- **Ngừng ghi khi ổ đĩa trống < X GB** (mặc định 1 GB; 0 = tắt): tạm ngừng ghi history để không làm đầy ổ / treo máy, tự ghi lại khi có chỗ.
Dữ liệu 8 giờ gần nhất trong RAM vẫn lấy mẫu 1 giây (Trend 1 phút … 8 giờ vẫn mượt); các cài đặt trên chỉ ảnh hưởng database lâu dài.
## Alarm
- **Không báo lại cùng alarm trong N giây sau khi vừa hết báo** (chống nhảy alarm): giá trị dao động quanh ngưỡng không còn sinh hàng nghìn dòng. `Tắt` = như trước.
- **Ghi xuống ổ đĩa mỗi N giây**: dùng chung với History.
## Ghi chú kỹ thuật
- Dọn dẹp, nén, thu nhỏ file đều chạy ở thread nền, xoá/nén theo từng đoạn nhỏ nên không làm đứng vòng lặp engine và không khoá ghi lâu (kể cả database hàng chục GB).
- Khi khởi động không còn kiểm tra dung lượng ngay (tránh xoá nhầm theo mặc định trước khi nạp cài đặt); việc dọn chạy nền ở lần ghi đầu tiên.
- Đã đổi `HistoryStore.StepSec / KeepDays / HeartbeatSec / MaxDbBytes` từ hằng số thành giá trị đọc theo cài đặt, nên Export, Báo cáo ngày, Trend chọn khoảng thời gian tự theo cài đặt mới.
Cơ chế hiện tại hợp lý cho một SCADA chạy trên một máy, nhưng còn vài điểm nên sửa. Mình chưa chạy thử với dữ liệu thật ở quy mô 100k tag, nên phần nhận định dưới đây dựa trên việc đọc code.
Ưu điểm
- Đơn giản, bền: SQLite một file, bật WAL nên vừa ghi vừa xem Trend không bị khoá. Không cần cài thêm dịch vụ nào, sao lưu chỉ là chép file.
- Ghi theo lô trong một transaction: nhẹ hơn nhiều so với ghi từng điểm. Dọn dẹp chạy nền, không làm đứng vòng lặp engine.
- Chia nhiều tầng: RAM giữ 1 giây/điểm cho đồ thị gần, database giữ 10 giây/điểm cho đồ thị xa. Chế độ chỉ ghi khi đổi và nén dữ liệu cũ giúp giảm dung lượng đáng kể.
- Tự giới hạn dung lượng: file không phình mãi, ổ đĩa sắp đầy thì tự ngừng ghi history.
Nhược điểm
- Ước lượng dung lượng dễ sai: mỗi dòng trend lưu cả tên tag dạng chữ, kèm thêm index theo thời gian, nên khoảng 50 byte/dòng. Với 2565 tag ghi đủ mỗi 10 giây (nếu tắt “chỉ lưu khi đổi”) thì khoảng 22 triệu dòng/ngày, tức 1 GB chỉ giữ được chừng 1 ngày. Mục “Giữ 30 ngày” khi đó không bao giờ đạt được.
- Xoá theo dung lượng là cách thô: xoá 10% thời gian cũ nhất của mọi tag, không phân biệt tag quan trọng. Với database hàng chục GB thì xoá cộng thu nhỏ file vẫn tạo ra các đợt ghi đĩa nặng.
- Nén dữ liệu cũ làm mất đỉnh: hiện chỉ giữ điểm đầu tiên mỗi khoảng, nên đỉnh và đáy bị mất, và không hoàn lại được.
- Ngưỡng bỏ qua dao động theo % là công cụ thô: cùng 1% nhưng tag nhiệt độ 500 °C khác hẳn tag dòng điện 0,2 A. Đúng ra phải đặt theo từng tag.
- Lỗi bị nuốt im lặng: database hỏng hoặc đĩa đầy thì không lưu gì mà không có cảnh báo. Khi ngừng ghi vì ổ đĩa đầy, thông báo chỉ hiện nếu có người mở khung cài đặt.
- Hàng đợi chờ ghi không giới hạn: nếu ghi lỗi liên tục thì hàng đợi trong RAM cứ lớn dần.
- Alarm thiếu các cơ chế chuẩn công nghiệp: chưa có hysteresis và chưa có trễ khi hết báo. Việc “không báo lại trong N giây” mình thêm chỉ là cách tạm. Khi khởi động lại, mọi alarm đang báo bị đóng với giờ hết báo là giờ khởi động, nên có thể sinh ra một alarm trùng. Alarm chưa Ack vẫn có thể bị xoá khi đầy dung lượng.
- Rủi ro từ bản vá của mình: lần nén đầu tiên trên database lớn có thể chạy rất lâu và tranh khoá ghi với việc lưu dữ liệu. Phần này mình chưa thử ở quy mô thật.
Nên cải thiện, theo thứ tự ưu tiên
- Ghi log lỗi và hiện trạng thái sức khoẻ lưu trữ (đang ghi, ngừng vì ổ đĩa, database lỗi), kèm giới hạn hàng đợi. Việc này nhỏ nhưng giá trị cao.
- Tự phục hồi khi
history.dbhoặcalarm.dbhỏng: đổi tên file hỏng rồi tạo file mới, như cách đã làm choproject.json. - Nén dữ liệu cũ bằng min/max/avg thay vì chỉ giữ điểm đầu.
- Chia database theo ngày hoặc tháng: mỗi ngày một file. Hết hạn thì xoá file, không cần xoá từng dòng hay thu nhỏ file nên rất nhẹ. Dùng tên tag dạng số nguyên thì mỗi dòng còn nhỏ hơn nhiều. Đây là thay đổi lớn nhất nhưng đáng làm nhất nếu bạn lưu dài ngày với nhiều tag.
- Cài đặt theo từng tag: ngưỡng bỏ qua dao động tính theo đơn vị kỹ thuật, và chu kỳ lưu riêng.
- Alarm: thêm hysteresis, trễ khi hết báo, giữ nguyên trạng thái alarm khi khởi động lại, và không xoá alarm chưa Ack.
- Đồ thị khoảng dài: server nên gộp bớt điểm (lấy min/max theo từng pixel) trước khi gửi. Mình chưa kiểm tra phần này trong code nên nên xem lại.
Mình khuyên làm 1, 2 và 3 trước vì ít rủi ro. Bạn muốn mình làm luôn nhóm đó, hay làm hẳn việc chia file theo ngày (mục 4)?

