مشروع تطبيقي: بناء نظام مراقبة صناعي متكامل بلغة Rust
نظرة عامة والهندسة المعمارية
نبني نظام مراقبة صناعي بأربع مراحل:
قارئ المستشعرات ──→ معالج البيانات ──→ محرك الإنذارات ──→ لوحة المتابعة
(sensor) (processor) (alarm) (dashboard)
↓ ↓ ↓ ↓
قراءة خام تصفية + معايرة قواعد + إشعارات عرض الحالة
كل مرحلة تستخدم مفاهيم تعلمناها: هياكل، سمات، أنماط، خيوط، async، واختبارات.
هيكل المشروع: مساحة عمل متعددة الحزم
نُطبّق ما تعلمناه في الدرس 11 عن مساحات العمل:
# Cargo.toml (جذر مساحة العمل)
[workspace]
members = [
"sensor",
"alarm",
"dashboard",
]
[workspace.dependencies]
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
factory-monitor/
├── Cargo.toml # مساحة العمل
├── sensor/
│ ├── Cargo.toml
│ └── src/lib.rs # قراءة وتحقق
├── alarm/
│ ├── Cargo.toml
│ └── src/lib.rs # قواعد وإشعارات
├── dashboard/
│ ├── Cargo.toml
│ └── src/main.rs # نقطة الدخول + العرض
└── tests/
└── integration.rs # اختبارات النظام الكامل
وحدة المستشعرات: قراءة البيانات والتحقق منها
// sensor/src/lib.rs
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SensorType {
Temperature,
Pressure,
Vibration,
}
#[derive(Debug, Clone)]
pub struct Reading {
pub sensor_id: u32,
pub sensor_type: SensorType,
pub value: f64,
pub timestamp: u64,
}
#[derive(Debug, PartialEq)]
pub enum ReadingError {
OutOfRange { value: f64, min: f64, max: f64 },
SensorOffline(u32),
InvalidData,
}
// سمة تُعرّف سلوك أي مستشعر
pub trait SensorReader {
fn read(&self) -> Result<Reading, ReadingError>;
fn sensor_id(&self) -> u32;
}
// تحقق من صحة القراءة حسب النوع
pub fn validate(reading: &Reading) -> Result<(), ReadingError> {
let (min, max) = match reading.sensor_type {
SensorType::Temperature => (-40.0, 200.0),
SensorType::Pressure => (0.0, 100.0),
SensorType::Vibration => (0.0, 50.0),
};
if reading.value < min || reading.value > max {
Err(ReadingError::OutOfRange {
value: reading.value, min, max
})
} else {
Ok(())
}
}
/// تطبيق معامل المعايرة على القراءة
pub fn apply_calibration(reading: &mut Reading, factor: f64) {
reading.value *= factor;
}
محرك الإنذارات: القواعد والإشعارات
// alarm/src/lib.rs
use sensor::{Reading, SensorType};
#[derive(Debug, Clone, PartialEq)]
pub enum AlarmLevel { Info, Warning, Critical, Emergency }
#[derive(Debug)]
pub struct AlarmRule {
pub sensor_id: u32,
pub threshold: f64,
pub level: AlarmLevel,
pub message: String,
}
#[derive(Debug)]
pub struct Alarm {
pub rule: AlarmRule,
pub actual_value: f64,
}
pub struct AlarmEngine {
rules: Vec<AlarmRule>,
}
impl AlarmEngine {
pub fn new() -> Self {
Self { rules: Vec::new() }
}
pub fn add_rule(&mut self, rule: AlarmRule) {
self.rules.push(rule);
}
/// تقييم القراءات وإرجاع الإنذارات المُطلَقة
pub fn evaluate(&self, readings: &[Reading]) -> Vec<Alarm> {
// نستخدم المُكررات: تصفية ← تحويل ← تجميع
self.rules.iter()
.filter_map(|rule| {
readings.iter()
.find(|r| r.sensor_id == rule.sensor_id)
.filter(|r| r.value > rule.threshold)
.map(|r| Alarm {
actual_value: r.value,
rule: AlarmRule {
sensor_id: rule.sensor_id,
threshold: rule.threshold,
level: rule.level.clone(),
message: rule.message.clone(),
},
})
})
.collect()
}
/// ترتيب الإنذارات: الطوارئ أولاً
pub fn sort_by_severity(alarms: &mut [Alarm]) {
alarms.sort_by(|a, b| {
let priority = |l: &AlarmLevel| match l {
AlarmLevel::Emergency => 0,
AlarmLevel::Critical => 1,
AlarmLevel::Warning => 2,
AlarmLevel::Info => 3,
};
priority(&a.rule.level).cmp(&priority(&b.rule.level))
});
}
}
بيئة التشغيل غير المتزامنة: مراقبة متوازية
// dashboard/src/main.rs
use tokio::sync::mpsc;
use tokio::time::{interval, Duration};
use sensor::{Reading, SensorType, validate};
use alarm::{AlarmEngine, AlarmRule, AlarmLevel};
// محاكاة قراءة مستشعر عبر الشبكة
async fn poll_sensor(id: u32, s_type: SensorType) -> Reading {
tokio::time::sleep(Duration::from_millis(50)).await;
Reading {
sensor_id: id,
sensor_type: s_type,
value: 25.0 + (id as f64 * 3.7) % 80.0,
timestamp: 0,
}
}
#[tokio::main]
async fn main() {
// قناة لإرسال القراءات من المهام إلى المعالج
let (tx, mut rx) = mpsc::channel::<Reading>(100);
// مهمة: استطلاع دوري للمستشعرات
let tx_clone = tx.clone();
tokio::spawn(async move {
let mut tick = interval(Duration::from_secs(5));
let sensors = vec![
(1, SensorType::Temperature),
(2, SensorType::Pressure),
(3, SensorType::Vibration),
];
loop {
tick.tick().await;
for (id, s_type) in &sensors {
let reading = poll_sensor(*id, s_type.clone()).await;
if validate(&reading).is_ok() {
let _ = tx_clone.send(reading).await;
}
}
}
});
drop(tx); // نُغلق المُرسل الأصلي
// إعداد محرك الإنذارات
let mut engine = AlarmEngine::new();
engine.add_rule(AlarmRule {
sensor_id: 1, threshold: 80.0,
level: AlarmLevel::Critical,
message: "حرارة الفرن مرتفعة".into(),
});
// المعالج الرئيسي: استقبال وتقييم
let mut batch = Vec::new();
while let Some(reading) = rx.recv().await {
println!(" مستشعر {}: {:.1}", reading.sensor_id, reading.value);
batch.push(reading);
if batch.len() >= 3 {
let mut alarms = engine.evaluate(&batch);
if !alarms.is_empty() {
AlarmEngine::sort_by_severity(&mut alarms);
for a in &alarms {
println!("[{:?}] {}", a.rule.level, a.rule.message);
}
}
batch.clear();
}
}
}
إضافة اختبارات للمسارات الحرجة
// sensor/src/lib.rs — داخل نفس الملف
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_temperature_valid_range() {
let r = Reading {
sensor_id: 1, sensor_type: SensorType::Temperature,
value: 75.0, timestamp: 0,
};
assert!(validate(&r).is_ok());
}
#[test]
fn test_pressure_out_of_range() {
let r = Reading {
sensor_id: 2, sensor_type: SensorType::Pressure,
value: 150.0, timestamp: 0,
};
assert!(validate(&r).is_err());
}
}
// tests/integration.rs — اختبار النظام الكامل
#[test]
fn test_alarm_triggers_on_high_temp() {
let mut engine = alarm::AlarmEngine::new();
engine.add_rule(alarm::AlarmRule {
sensor_id: 1, threshold: 80.0,
level: alarm::AlarmLevel::Critical,
message: "حرارة مرتفعة".into(),
});
let readings = vec![sensor::Reading {
sensor_id: 1, sensor_type: sensor::SensorType::Temperature,
value: 95.0, timestamp: 0,
}];
let alarms = engine.evaluate(&readings);
assert_eq!(alarms.len(), 1);
assert_eq!(alarms[0].rule.level, alarm::AlarmLevel::Critical);
}
تشغيل النظام الكامل
# تشغيل جميع الاختبارات أولاً
cargo test --workspace
# ثم تشغيل النظام
cargo run -p dashboard
المخرجات المتوقعة:
مستشعر 1: 28.7°C
مستشعر 2: 32.4 bar
مستشعر 3: 11.1 mm/s
---
مستشعر 1: 88.3°C
[Critical] حرارة الفرن مرتفعة
الخلاصة والخطوات القادمة
في هذه السلسلة من 15 درساً تعلمنا:
- الأساسيات: المتغيرات، الأنواع، التحكم في التدفق
- الملكية: نظام Rust الفريد لإدارة الذاكرة بأمان
- الهياكل والسمات: بناء أنظمة مرنة وقابلة للتوسيع
- معالجة الأخطاء:
ResultوOptionبدلاً من الانهيار - المجموعات والمكررات: معالجة بيانات المستشعرات بكفاءة
- البرمجة غير المتزامنة: مراقبة مئات المستشعرات بموارد قليلة
- الاختبارات: ضمان أن الكود الصناعي يعمل بشكل صحيح
خطوات للاستمرار في التعلم:
- Rust المدمج:
embedded-halوno_stdللتحكم المباشر بوحدات PLC - أطر الويب: Axum أو Actix لبناء لوحات متابعة عبر الإنترنت
- قواعد البيانات: SurrealDB أو SQLx لتخزين بيانات المستشعرات
- MQTT/OPC-UA: بروتوكولات الاتصال الصناعية
- WebAssembly: تشغيل كود Rust في المتصفح للوحات تفاعلية
كل درس في هذه السلسلة أعطاك أداة — الآن لديك صندوق أدوات كامل لبناء أنظمة صناعية آمنة وفعّالة بلغة Rust.