[{"server":null,"owner":null,"id":"2c82c7eb-3922-40dd-be40-f81ab6e0a325","params":{"result":{"value":null,"datetime":1699001878,"status":null,"version":0},"hash":{"value":"Vqm/1eu5XuC3z3JfE2s2vQ==","datetime":1702978854,"status":null,"version":0},"enabled":{"value":false,"datetime":1701267245,"status":null,"version":0},"error_text":{"value":"","datetime":1700994350,"status":null,"version":0},"type":{"value":"EgsScenario","datetime":1702978732,"status":null,"version":0},"settings":{"value":"{\r\n  \"alarm_timeout\": \"00:00:30\",\r\n  \"interval\": \"00:00:01\",\r\n  \"precedence\": {\r\n    \"alarm_unlikely\": 10.0,\r\n    \"alarm_likely\": 90,\r\n    \"alarm_alarm\": 140\r\n  },\r\n  \"alarm_info\": {\r\n    \"alarms_only\": true,\r\n    \"changes_only\": true\r\n  },\r\n  \"importance\": {\r\n    \"VideoCamera\": {\r\n      \"alarm_fixObjectOn\": 2,\r\n      \"alarm_objectClassFound\": 2,\r\n      \"alarm_motionInZone\": 2,\r\n      \"alarm_reversBarrier\": 0,\r\n      \"alarm_ObjectOn\": 0,\r\n      \"alarm_objectRemoved\": 0,\r\n      \"alarm_vehicleMoveInProhibitedDirection\": 0,\r\n      \"alarm_objectLeft\": 0\r\n    },\r\n    \"LowCurrentSirenMeta\": {\r\n      \"*\": 0\r\n    }\r\n  }\r\n}","datetime":1700991902,"status":null,"version":0},"state":{"value":"ok.normal","datetime":1700994350,"status":null,"version":0},"script":{"value":"# имя: 'Smart Zone'\r\n# описание: рассчитывание состояния зоны\r\n# тип триггера: 'EgsScenario'\r\n# создан: 2019.05.07 14.28.41, Сельченков Н.Ю.\r\n# изменен: '2023.12.19 13.40.56', Сельченков Н.Ю.\r\n# подробности: https://redmine.integra-s.com:11000/projects/eilyacuario/wiki/Smart_Zone\r\n\r\nuse System.IO.File\r\nuse System.IO.Path\r\nuse acuario2.utils.TextExtension\r\nuse acuario2.utils.DateTimeExtension\r\nuse Newtonsoft.Json.JsonConvert from Newtonsoft.Json\r\nuse System.Collections.Generic.Dictionary(string, object) as Dictionary\r\nuse System.Collections.Generic.Dictionary(Zone_states, double) as PrecedenceDictionary\r\nuse System.Collections.Concurrent.ConcurrentDictionary(Zone, AbstractObject) as UpdatedZones\r\n\r\nuse json_schema\r\n`\r\n{\r\n    \"type\": \"object\",\r\n    \"properties\":\r\n    {\r\n        \"interval\":            { \"type\": \"string\", \"format\": \"TimeSpan\", \"default\": \"00:00:01\" },\r\n        \"alarm_timeout\":       { \"type\": \"string\", \"format\": \"TimeSpan\", \"default\": \"00:00:15\" },\r\n        \"precedence\":\r\n        {\r\n            \"type\": \"object\",\r\n            \"additionalProperties\": { \"type\": \"number\" }\r\n        },\r\n        \"alarm_info\":\r\n        {\r\n            \"type\": \"object\",\r\n            \"properties\":\r\n            {\r\n                \"alarms_only\":   { \"type\": \"boolean\", \"default\": true },\r\n                \"changes_only\":  { \"type\": \"boolean\", \"default\": true }\r\n            }\r\n        },\r\n        \"importance\":      \r\n        { \r\n            \"type\": \"object\",\r\n            \"additionalProperties\": \r\n            { \r\n                \"type\": \"object\",\r\n                \"additionalProperties\": { \"type\": \"number\" }\r\n            }\r\n        }     \r\n    }\r\n}\r\n` as SETTINGS\r\n\r\nlet settings          = SETTINGS(this.settings)\r\nlet updated_zones     = UpdatedZones()\r\nlet state_precedences = PrecedenceDictionary()\r\n\r\nfrom settings.precedence do \r\n    let state = Zone_states(Enum.Parse(Zone_states as Type, Key))\r\n    state_precedences[state] = Value \r\nnow\r\n\r\nuse new \r\n{ \r\n    item       = null as Item,\r\n    state      = \"\",  \r\n    datetime   = DateTime.MinValue,   \r\n    weight     = 0.0,\r\n    zonenames  = null as string[],\r\n    precedence = 0.0,\r\n    importance = 0.0,\r\n    value      = \"\" \r\n} as WeightedItem\r\n\r\nuse new \r\n{ \r\n    type       = \"\", \r\n    state      = \"\", \r\n    datetime   = DateTime.MinValue,\r\n    weight     = 0.0,\r\n    precedence = 0.0,\r\n    importance = 0.0,\r\n    alarm      = false, \r\n    value      = \"\"\r\n} as MemberInfo\r\n\r\nuse System.Collections.Generic.Dictionary(Guid, MemberInfo) as MemeberInfoDictionary\r\n\r\nuse new \r\n{ \r\n    alarm_factor     = 0.0,\r\n    alarm_precedence = 0.0,\r\n    state_before     = \"\", \r\n    state_after      = \"\",\r\n    members          = null as MemeberInfoDictionary,\r\n    hash             = \"\" \r\n} as AlarmInfo\r\n\r\nlet @alarm(state) = string(state) like \"alarm_*\"\r\n\r\nlet intersected_with(seq1, seq2) = from seq1 intersect seq2 count all > 0\r\n\r\nlet get_state_importance(state as Param) = \r\n    let importance = 1\r\n    from settings.importance where Key in state.Owner.Types \r\n    select many (from Value where state.Text like Key select Value) \r\n    take 1 do \r\n        importance = it\r\n    now\r\n    importance  \r\nend\r\n\r\nlet recently_alarmed(item as Item) = \r\n    (DateTime.UtcNow - item.LastAlarm <= settings.alarm_timeout) and\r\n    (item.RecentAlarm isnt null)  \r\n\r\nlet get_alarm_state(alarm_precedence as double) = \r\n    from state_precedences \r\n        where alarm_precedence >= Value\r\n        order by Value\r\n        select Key \r\n        try last\r\n        \r\nlet get_weighted_items(zone as Zone) =\r\n    from zone.Pins    of type ZoneOutputPin\r\n    select many Links of type ZoneLink \r\n    where @in isnt null select\r\n        let item        = @in.Owner\r\n        let state       = if item is recently_alarmed then item.RecentAlarm else item[\"state\"]\r\n        let elem        = WeightedItem()\r\n        elem.item       = item\r\n        elem.state      = state.Text\r\n        elem.datetime   = state.DateTime\r\n        elem.weight     = check weight >= 0 else 1\r\n        elem.zonenames  = detection_zonenames ?? empty \r\n        elem.precedence = (item as BaseObject)?.precedence \r\n        elem.precedence = check 0 <= elem.precedence #<= 100 \r\n        elem.importance = get_state_importance(state)   \r\n        elem.value      = item[\"state\"].Text\r\n        elem \r\n    group by item select (from it first)    \r\n    to array\r\nend\r\n\r\nlet update_alarm_info(zone as Zone, state as Zone_states, items as WeightedItem[], alarm_precedence as double) = \r\n    let alarms_only       = settings.alarm_info.alarms_only\r\n    let info              = AlarmInfo()\r\n    info.alarm_factor     = zone.alarm_factor\r\n    info.alarm_precedence = alarm_precedence\r\n    info.state_before     = string(zone.state)\r\n    info.state_after      = string(state)\r\n    info.members          = from items where not alarms_only or (it.state is @alarm) bind item.Id to \r\n                                let info        = MemberInfo()\r\n                                info.type       = it.item.Types[1] \r\n                                info.state      = it.state\r\n                                info.datetime   = it.datetime\r\n                                info.weight     = it.weight\r\n                                info.precedence = it.precedence\r\n                                info.importance = it.importance\r\n                                info.alarm      = it.state is @alarm\r\n                                info.value      = it.value\r\n                                info  \r\n    let alarm_members = from items where it.state is @alarm order by item.Id \r\n                        select new { id=item.Id, datetime=datetime, state=state } to array\r\n    info.hash         = TextExtension.ToHash(JsonConvert.SerializeObject(alarm_members), \"MD5\") \r\n    zone.alarm_info   = string(info)\r\nend\r\n   \r\nlet having_event(zone as Zone) = \r\n    from graph.Events of type StateEvent any owner is string(zone.Id)\r\nend\r\n\r\nlet get_camera_detection_zonenames(camera as VideoCamera) =\r\n    let dicts = try JsonConvert.DeserializeObject(camera.detection_zone, Dictionary[]) as Dictionary[]\r\n    from dicts ?? empty select it[\"zonename\"] as string to array\r\nend\r\n\r\nlet check_detection_zones(zone as Zone, items as WeightedItem[]) =\r\n    from items where (item is VideoCamera) and (state is @alarm) and (zonenames isnt empty) do\r\n        let camera_zonenames = get_camera_detection_zonenames(it.item as VideoCamera)\r\n        if zonenames isnt intersected_with camera_zonenames then\r\n            it.state = \"ok_normal\"\r\n    now\r\nend\r\n\r\nlet process_state(zone as Zone, may_reset_state as bool) =     \r\n    let changes_only = settings.alarm_info.changes_only\r\n    let state        = zone.state \r\n    let datetime     = zone[\"state\"].DateTime \r\n    let items        = get_weighted_items(zone)\r\n    check_detection_zones(zone, items)\r\n    \r\n    let prev_preced  = if state in state_precedences then state_precedences[state]\r\n    let curr_preced  = from items where (it.state is @alarm) select (weight * precedence * importance) sum\r\n    let new_alarm    = from items any   (it.value is @alarm) and (it.datetime > datetime)\r\n    let curr_preced  = curr_preced / zone.alarm_factor\r\n    let alarm_state  = get_alarm_state(curr_preced)     \r\n\r\n    let zone_is_empty = items is empty\r\n    let maybe_ok      = (prev_preced is 0) or (curr_preced is 0) \r\n    let alarm_in_zone = (alarm_state is @alarm) and (curr_preced > prev_preced)\r\n\r\n    if zone_is_empty      then state = \"none_unknown\"\r\n    else if alarm_in_zone then state = alarm_state\r\n    else if maybe_ok      then state = \"ok_normal\"\r\n                    \r\n    let changed = zone.state isnt state\r\n    if new_alarm or (not changes_only) or changed or (zone.alarm_info is empty) then \r\n        update_alarm_info(zone, state, items, curr_preced) \r\n     \r\n    if (zone is having_event) or not new_alarm then zone.state = state\r\n    else zone[\"state\"].Set(state, null, DateTime.UtcNow)        \r\nend\r\n\r\nlet process_command(zone as Zone) = \r\n    let items = get_weighted_items(zone)\r\n    from items where weight > 0 select item do\r\n        let source = zone[\"command\"]\r\n        let target =   it[\"command\"]\r\n        if source.Text in target.EnumNames then target.Set(source.Text, source.Status, source.DateTime) \r\n        else target.Set(\"DEFAULT\", Status.invalid, source.DateTime)\r\n    now\r\nend\r\n\r\nlet on_update(obj as AbstractObject, changes as string[]) = \r\n    if obj is Item item then \r\n        if (\"state\"      in changes) or\r\n           (\"precedence\" in changes) then \r\n            from item.GetItemsLinkedTo(ZoneInputPin) of type Zone do \r\n                updated_zones[it] = obj \r\n            now\r\n    end\r\n    \r\n    if obj is ZoneLink link then\r\n        if \"weight\" in changes then\r\n            let zone = link.@out.Owner as Zone \r\n            if zone isnt null then\r\n                updated_zones[zone] = obj\r\n    end\r\n    \r\n    if obj is Zone zone then \r\n        if \"command\" in changes then \r\n            process_command(zone) \r\n\r\n        if (\"state\"        in changes) or\r\n           (\"alarm_factor\" in changes) then \r\n            updated_zones[zone] = obj\r\n    end\r\n    \r\n    void()\r\nend\r\n\r\nlet process_updated_zones() = \r\n    let zones = from updated_zones select Key to array\r\n    updated_zones.Clear()\r\n    from zones do process_state(it, true) now\r\nend\r\n\r\nlet process_all_zones() = from graph.Values of type Zone do process_state(it, false) now\r\n\r\nthis.RunOnUpdated(on_update)\r\nthis.RunOnTimer(settings.interval.TotalSeconds, process_updated_zones)\r\nthis.RunOnTimer(settings.alarm_timeout.TotalSeconds, process_all_zones)\r\n\r\nprocess_all_zones()\r\n\r\n#from 0 to 11 do print(it*9, get_alarm_state(it*9)) now\r\n\r\n\r\n        ","datetime":1702978856,"status":null,"version":0},"name":{"value":"Smart Zone","datetime":1699001878,"status":null,"version":0}},"entity":"item","operation":"create"}]