Device Rule Buffer
Rule Service, tüm kural değerlendirme durumunu Redis'te device:{device_id} anahtarı altında saklar. Eski sistemin yapısını koruyarak üzerine yeni alanlar eklendi.
Anahtar Yapısı
Key: device:{device_id}
TTL: No limit (cihaz aktif olduğu sürece persistence)
Type: JSON
Size: ~50-200 KB (grup sayısına bağlı)
Örnek: device:02O0000C49DFA70
Schema
{
"{group_id}": {
// ── Tanım Alanları ─────────────────────────────────────────────────
// DB'den yüklenir; rule_hash değiştiğinde refresh edilir
"ID": 5099,
"Device_ID": "02O0000C49DFA70",
"Name": "Pump Start",
"Description": "Pump Start Rule for Device 02O0000C49DFA70",
"Match_Type": "all", // all | any | not
"Priority": 1, // Grup değerlendirme sırası (düşük = önce)
"Multi_Trigger": false,
"Cooldown_Sec": 0, // multi_trigger=true ise asgari tetiklenme aralığı (saniye)
"Notify_on_Reset": true,
"Status": true,
"Publish_Bit": 0,
"Valid_From": null, // ISO8601 veya null; null = başlangıç sınırı yok
"Valid_To": null, // ISO8601 veya null; null = bitiş sınırı yok
"Rule_Hash": "a8be3f9c2e1d...", // Cache invalidation için
"Create_Time": "2025-04-29T17:51:54.544182+03:00",
"Update_Time": "2025-07-30T16:09:05.678848+03:00",
// ── Runtime State Alanları ─────────────────────────────────────────
// Sadece Rule Service tarafından güncellenir
"Triggered": false,
"Trigger_Count": 194,
"Last_Trigger_Time": null, // ISO8601 veya null
"Last_Reset_Time": null, // ISO8601 veya null
"Last_Fired_Time": null, // ISO8601 veya null; multi_trigger cooldown kontrolü için
// ── Aksiyon Tanımları ──────────────────────────────────────────────
// DB'den yüklenir; rule_group_actions + rule_actions join
"Trigger": {
"Action_ID": 21,
"Inbox": true,
"Push_Template_ID": 5,
"Device_Command_ID": null,
"Notify_Owner": true,
"Notify_Subusers": false,
"Notify_Technician": true,
"Notify_Admin": false
},
"Reset": {
"Action_ID": 22,
"Inbox": true,
"Push_Template_ID": 6,
"Device_Command_ID": null,
"Notify_Owner": true,
"Notify_Subusers": false,
"Notify_Technician": false,
"Notify_Admin": false
},
// ── Kural Tanımı + Runtime ─────────────────────────────────────────
// Anahtarlar: rule.id (string)
"Rules": {
"{rule_id}": {
// Tanım alanları (DB'den)
"ID": 114,
"Type": "threshold", // threshold | duration | register_transition | register_bit | stream_gap
"Variable_ID": "VRMS_R",
"Operator": ">", // > | >= | < | <= | == | !=
"Value": 300.0,
"Priority": 0, // Grup içi kural sırası (düşük = önce)
"Duration_Sec": null,
"Old_Register_Value": null, // register_transition için
"New_Register_Value": null, // register_transition için
"Bit_Index": null, // register_bit için
"Bit_Expected": null, // register_bit için (true | false)
"Status": true,
// Runtime alanlar (Rule Service tarafından güncellenir)
"Duration_Counter": 0, // Biriken saniye (duration type için)
"Previous_Register_Value": null, // register_transition için önceki değer
"Last_Result": null, // Son değerlendirme sonucu (true | false)
"Last_Evaluated": null // ISO8601 | null
}
},
// ── Publish Cache ──────────────────────────────────────────────────
// measurement_register.publish'ten okunan son değer
"Publish_Cache": {
"Enabled": true, // Son okunan publish durumu
"Last_Checked": "2026-04-14T10:00:00Z"
}
}
}
Gerçek Dünya Örneği
Üç farklı kural tipi içeren tek bir cihaz grubu:
{
"5099": {
"ID": 5099,
"Device_ID": "02O0000C49DFA70",
"Name": "Pump Start",
"Description": "Pump Start Rule for Device 02O0000C49DFA70",
"Match_Type": "all",
"Priority": 1,
"Multi_Trigger": false,
"Cooldown_Sec": 0,
"Notify_on_Reset": true,
"Status": true,
"Publish_Bit": 0,
"Valid_From": null,
"Valid_To": null,
"Rule_Hash": "a8be3f9c2e1d...",
"Create_Time": "2025-04-29T17:51:54.544182+03:00",
"Update_Time": "2025-07-30T16:09:05.678848+03:00",
"Triggered": false,
"Trigger_Count": 194,
"Last_Trigger_Time": "2026-04-14T09:15:30.000+03:00",
"Last_Reset_Time": "2026-04-14T09:22:10.000+03:00",
"Last_Fired_Time": "2026-04-14T09:15:30.000+03:00",
"Trigger": {
"Action_ID": 21,
"Inbox": true,
"Push_Template_ID": 5,
"Device_Command_ID": null,
"Notify_Owner": true,
"Notify_Subusers": false,
"Notify_Technician": true,
"Notify_Admin": false
},
"Reset": {
"Action_ID": 22,
"Inbox": true,
"Push_Template_ID": 6,
"Device_Command_ID": null,
"Notify_Owner": true,
"Notify_Subusers": false,
"Notify_Technician": false,
"Notify_Admin": false
},
"Rules": {
"114": {
"ID": 114,
"Type": "threshold",
"Variable_ID": "VRMS_R",
"Operator": ">",
"Value": 300.0,
"Priority": 0,
"Duration_Sec": null,
"Old_Register_Value": null,
"New_Register_Value": null,
"Bit_Index": null,
"Bit_Expected": null,
"Status": true,
"Duration_Counter": 0,
"Previous_Register_Value": null,
"Last_Result": true,
"Last_Evaluated": "2026-04-14T09:15:30.000+03:00"
},
"115": {
"ID": 115,
"Type": "duration",
"Variable_ID": "IRMS_R",
"Operator": ">",
"Value": 50.0,
"Priority": 1,
"Duration_Sec": 600,
"Old_Register_Value": null,
"New_Register_Value": null,
"Bit_Index": null,
"Bit_Expected": null,
"Status": true,
"Duration_Counter": 350,
"Previous_Register_Value": null,
"Last_Result": false,
"Last_Evaluated": "2026-04-14T09:15:30.000+03:00"
},
"116": {
"ID": 116,
"Type": "register_transition",
"Variable_ID": "device_status",
"Operator": null,
"Value": null,
"Priority": 2,
"Duration_Sec": null,
"Old_Register_Value": "0x0101",
"New_Register_Value": "0x0111",
"Bit_Index": null,
"Bit_Expected": null,
"Status": true,
"Duration_Counter": 0,
"Previous_Register_Value": "0x0101",
"Last_Result": false,
"Last_Evaluated": "2026-04-14T09:15:30.000+03:00"
}
},
"Publish_Cache": {
"Enabled": true,
"Last_Checked": "2026-04-14T09:00:00.000+03:00"
}
}
}
Alan Referans Tablosu
Grup Seviyesi
| Alan | Tip | Kaynak | Açıklama |
|---|---|---|---|
ID | integer | DB | rule_groups.id |
Device_ID | string | DB | Cihaz kimliği |
Name | string | DB | Grup adı |
Description | string | DB | Açıklama |
Match_Type | string | DB | all / any / not |
Priority | integer | DB | Grup değerlendirme sırası |
Multi_Trigger | boolean | DB | Level / edge tetikleme |
Cooldown_Sec | integer | DB | Ardışık tetiklenme arası minimum süre (saniye) |
Notify_on_Reset | boolean | DB | Sıfırlamada aksiyon üretilsin mi |
Status | boolean | DB | Grup aktif/pasif |
Publish_Bit | integer | DB | Push gating bit indeksi |
Valid_From | string/null | DB | Aktiflik başlangıcı; null = sınır yok |
Valid_To | string/null | DB | Aktiflik bitişi; null = sınır yok |
Rule_Hash | string | DB | Cache invalidation hash |
Create_Time | string | DB | Oluşturma zamanı |
Update_Time | string | DB | Son güncelleme zamanı |
Triggered | boolean | Runtime | Güncel tetiklenme durumu |
Trigger_Count | integer | Runtime | Toplam tetiklenme sayısı |
Last_Trigger_Time | string/null | Runtime | Son tetiklenme zamanı |
Last_Reset_Time | string/null | Runtime | Son sıfırlanma zamanı |
Last_Fired_Time | string/null | Runtime | Cooldown kontrolü için son gerçek tetiklenme zamanı |
Kural Seviyesi (Rules)
| Alan | Tip | Kaynak | Açıklama |
|---|---|---|---|
ID | integer | DB | rules.id |
Type | string | DB | threshold / duration / register_transition / register_bit / stream_gap |
Variable_ID | string | DB | Değerlendirilecek değişken |
Operator | string/null | DB | Karşılaştırma operatörü |
Value | float/null | DB | Eşik değeri |
Priority | integer | DB | Grup içi sıralama |
Duration_Sec | integer/null | DB | Süreli kural için hedef saniye |
Old_Register_Value | string/null | DB | Transition başlangıç maskesi |
New_Register_Value | string/null | DB | Transition bitiş maskesi |
Bit_Index | integer/null | DB | register_bit için bit pozisyonu |
Bit_Expected | boolean/null | DB | register_bit için beklenen değer |
Status | boolean | DB | Kural aktif/pasif |
Duration_Counter | integer | Runtime | Biriken saniye (duration için) |
Previous_Register_Value | string/null | Runtime | Önceki register değeri (transition için) |
Last_Result | boolean/null | Runtime | Son değerlendirme sonucu |
Last_Evaluated | string/null | Runtime | Son değerlendirme zamanı |
Cache Yaşam Döngüsü
Yükleme (Load)
1. Cihaz ilk kez değerlendirildiğinde:
Redis.get("device:{device_id}") → null
2. DB'den yükle:
SELECT * FROM rule_groups
JOIN device_rule_assignments USING (id)
LEFT JOIN rules ON rules.group_id = rule_groups.id
LEFT JOIN device_rule_state ON ...
WHERE device_id = ?
3. Buffer oluştur (definition + state + empty runtime fields)
4. Redis.set("device:{device_id}", JSON, {TTL: "no-limit"})
Güncelleme (Update on Trigger/Reset)
Atomik JSON patch (RedisJSON veya GET → mutate → SET):
1. Redis.get("device:{device_id}") → buffer
2. buffer[group_id].Triggered = true
3. buffer[group_id].Trigger_Count += 1
4. buffer[group_id].Last_Trigger_Time = now()
5. buffer[group_id].Rules[rule_id].Duration_Counter = ...
6. buffer[group_id].Rules[rule_id].Last_Result = true
7. buffer[group_id].Rules[rule_id].Last_Evaluated = now()
8. Redis.set("device:{device_id}", buffer)
Invalidation (Rule Tanımı Değişince)
DB'de rule_group update edildiğinde:
1. Yeni Rule_Hash hesapla
2. Redis.get("device:{device_id}") → buffer
3. buffer[group_id].Rule_Hash ile karşılaştır
4. Farklıysa: buffer[group_id] definition alanlarını DB'den yenile
5. Runtime alanları koru (Triggered, Trigger_Count, Duration_Counter, ...)
6. Redis.set("device:{device_id}", buffer)
Silinme (Delete)
Cihaz atamadan kaldırıldığında:
Redis.get("device:{device_id}") → buffer
delete buffer[group_id]
Redis.set("device:{device_id}", buffer)
Cihaz tamamen silindiğinde:
Redis.del("device:{device_id}")
Özel Durumlar
Duration Counter Mantığı
Rule tipi = "duration", Duration_Sec = 600
window.ready.v1 geldiğinde:
if (threshold_koşul_sağlandı):
buffer[gid].Rules[rid].Duration_Counter += window_interval_sec
else:
buffer[gid].Rules[rid].Duration_Counter = 0 // Koşul bozuldu, başa dön
if (Duration_Counter >= Duration_Sec):
Rule değerlendirmesi = true // Kural tetiklenmeye hazır
Register Transition Mantığı
Rule tipi = "register_transition"
Old_Register_Value = "0x0101"
New_Register_Value = "0x0111"
Yeni window geldiğinde:
current_val = event.data.status_register.value // "0x0111"
previous_val = buffer[gid].Rules[rid].Previous_Register_Value // "0x0101"
if (mask_match(previous_val, Old_Register_Value)
AND mask_match(current_val, New_Register_Value)):
Rule değerlendirmesi = true
else:
Rule değerlendirmesi = false
// Her durumda previous_val güncelle:
buffer[gid].Rules[rid].Previous_Register_Value = current_val
Publish Cache Yenilenmesi
Publish_Cache.Last_Checked kontrol et:
if (now() - Last_Checked > 60s):
publish = DB.get("measurement_register.publish WHERE device_id = ?")
buffer[gid].Publish_Cache.Enabled = publish
buffer[gid].Publish_Cache.Last_Checked = now()
else:
publish = buffer[gid].Publish_Cache.Enabled // Cache kullan
Okuma / Yazma Sahibi
| İşlem | Yapan | Notlar |
|---|---|---|
| İlk yükleme | Rule Service | DB fallback sonrası |
| Definition refresh | Rule Service | Rule_Hash uyuşmazlığında |
| Runtime state yazma | Yalnız Rule Service | Başka servis yazmamalı |
| Publish_Cache yazma | Rule Service | 60 sn refresh |
| Group silme | Rule Service | Assignment kaldırıldığında |
| Cihaz silme | Rule Service | Cihaz silindiğinde |
Önemli
Redis buffer'daki definition alanları (Name, Match_Type, Priority vb.) görüntüleme amaçlıdır; doğruluk kaynağı DB'dir. Runtime alanlar (Triggered, Trigger_Count, Duration_Counter vb.) ise Redis'te canonical olarak tutulur ve her tetiklenme/sıfırlamada hem DB'ye hem Redis'e yazılır.