Ana içeriğe geç

← Rule Service Ana Sayfası

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

AlanTipKaynakAçıklama
IDintegerDBrule_groups.id
Device_IDstringDBCihaz kimliği
NamestringDBGrup adı
DescriptionstringDBAçıklama
Match_TypestringDBall / any / not
PriorityintegerDBGrup değerlendirme sırası
Multi_TriggerbooleanDBLevel / edge tetikleme
Cooldown_SecintegerDBArdışık tetiklenme arası minimum süre (saniye)
Notify_on_ResetbooleanDBSıfırlamada aksiyon üretilsin mi
StatusbooleanDBGrup aktif/pasif
Publish_BitintegerDBPush gating bit indeksi
Valid_Fromstring/nullDBAktiflik başlangıcı; null = sınır yok
Valid_Tostring/nullDBAktiflik bitişi; null = sınır yok
Rule_HashstringDBCache invalidation hash
Create_TimestringDBOluşturma zamanı
Update_TimestringDBSon güncelleme zamanı
TriggeredbooleanRuntimeGüncel tetiklenme durumu
Trigger_CountintegerRuntimeToplam tetiklenme sayısı
Last_Trigger_Timestring/nullRuntimeSon tetiklenme zamanı
Last_Reset_Timestring/nullRuntimeSon sıfırlanma zamanı
Last_Fired_Timestring/nullRuntimeCooldown kontrolü için son gerçek tetiklenme zamanı

Kural Seviyesi (Rules)

AlanTipKaynakAçıklama
IDintegerDBrules.id
TypestringDBthreshold / duration / register_transition / register_bit / stream_gap
Variable_IDstringDBDeğerlendirilecek değişken
Operatorstring/nullDBKarşılaştırma operatörü
Valuefloat/nullDBEşik değeri
PriorityintegerDBGrup içi sıralama
Duration_Secinteger/nullDBSüreli kural için hedef saniye
Old_Register_Valuestring/nullDBTransition başlangıç maskesi
New_Register_Valuestring/nullDBTransition bitiş maskesi
Bit_Indexinteger/nullDBregister_bit için bit pozisyonu
Bit_Expectedboolean/nullDBregister_bit için beklenen değer
StatusbooleanDBKural aktif/pasif
Duration_CounterintegerRuntimeBiriken saniye (duration için)
Previous_Register_Valuestring/nullRuntimeÖnceki register değeri (transition için)
Last_Resultboolean/nullRuntimeSon değerlendirme sonucu
Last_Evaluatedstring/nullRuntimeSon 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

İşlemYapanNotlar
İlk yüklemeRule ServiceDB fallback sonrası
Definition refreshRule ServiceRule_Hash uyuşmazlığında
Runtime state yazmaYalnız Rule ServiceBaşka servis yazmamalı
Publish_Cache yazmaRule Service60 sn refresh
Group silmeRule ServiceAssignment kaldırıldığında
Cihaz silmeRule ServiceCihaz 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.