[{"server":null,"owner":null,"id":"2927194a-c5d3-44bc-baef-1e5e054bd4e5","params":{"result":{"value":null,"datetime":1596644327,"status":null,"version":0},"hash":{"value":"PMtr+b7qcVfrfA0plfOn7A==","datetime":1596700084,"status":null,"version":0},"enabled":{"value":true,"datetime":1596644327,"status":null,"version":0},"error_text":{"value":null,"datetime":1596644327,"status":null,"version":0},"type":{"value":"EgsScenario","datetime":1596700015,"status":null,"version":0},"settings":{"value":"{\r\n  \"alarm_timeout\": \"00:00:15\",\r\n  \"interval\": \"00:00:01\",\r\n  \"precedence\": {\r\n    \"alarm_likely\": 60,\r\n    \"alarm_alarm\": 90\r\n  },\r\n  \"alarm_info\": {\r\n    \"alarms_only\": false,\r\n    \"changes_only\": false\r\n  },\r\n  \"importance\": {\r\n    \"BOLID_ContactSensor\": {\r\n      \"alarm_openCase\": 2.0\r\n    }\r\n  }\r\n}","datetime":1596699712,"status":null,"version":0},"state":{"value":"ok.normal","datetime":1596644327,"status":null,"version":0},"script":{"value":"# имя: 'Smart Zone'\r\n# описание: рассчитывание состояния зоны\r\n# тип триггера: 'EgsScenario'\r\n# создан: 2019.05.07 14.28.41, Сельченков Н.Ю.\r\n# изменен: '2020.08.06 11.47.50', Сельченков Н.Ю.\r\n# подробности: https://redmine.integra-s.com:11000/projects/eilyacuario/wiki/Smart_Zone\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:05\" },\r\n        \"precedence\":\r\n        {\r\n            \"type\": \"object\",\r\n            \"properties\":\r\n            {\r\n                \"alarm_likely\":   { \"type\": \"integer\", \"default\": 60 },\r\n                \"alarm_alarm\":    { \"type\": \"integer\", \"default\": 90 }\r\n            }\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\n\r\nuse new \r\n{ \r\n    item       = null as Item,\r\n    weight     = 0.0,\r\n    precedence = 0.0,\r\n    importance = 0.0,\r\n    error      = false,\r\n    alarm      = false\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} as MemberInfo\r\n\r\nuse System.Collections.Generic.Dictionary(Guid, MemberInfo) as MemeberInfoDictionary\r\n\r\nuse new \r\n{ \r\n    alarm_weight     = 0.0,\r\n    alarm_precedence = 0.0,\r\n    state_before     = \"\", \r\n    state_after      = \"\",\r\n    members          = null as MemeberInfoDictionary\r\n} as AlarmInfo\r\n\r\nlet @alarm(state) = string(state) like \"alarm_*\"\r\n\r\nlet get_state_importance(item as Item) = \r\n    let importance = 1\r\n    let state      = item[\"state\"].Text \r\n    from settings.importance where Key in item.Types \r\n    select many (from Value where state 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 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       = item.State\r\n        let elem        = WeightedItem()\r\n        elem.item       = item\r\n        elem.weight     = check weight >= 0 else 1\r\n        elem.precedence = (item as BaseObject)?.precedence \r\n        elem.precedence = check 0 <= elem.precedence #<= 100 \r\n        elem.importance = get_state_importance(item) \r\n        elem.error      = (state is \"error\") or (state is \"none\") or (state is \"DEFAULT\")  \r\n        elem.alarm      = (state is \"alarm\") or (DateTime.UtcNow - item.LastAlarm <= settings.alarm_timeout)\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_weight     = zone.alarm_weight\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 alarm bind item.Id to \r\n                                let info        = MemberInfo()\r\n                                info.type       = item.Types[1] \r\n                                info.state      = item[\"state\"].Text\r\n                                info.datetime   = item[\"state\"].DateTime\r\n                                info.weight     = weight\r\n                                info.precedence = precedence\r\n                                info.importance = importance\r\n                                info.alarm      = alarm\r\n                                info\r\n    zone.alarm_info = string(info)\r\nend\r\n\r\nlet process_state_by_weight(zone as Zone) = \r\n    let changes_only = settings.alarm_info.changes_only\r\n    let state        = zone.state \r\n    let items        = get_weighted_items(zone)\r\n                \r\n    let alarm_weight = from items where it.alarm select weight sum\r\n    \r\n    let zone_is_empty = items is empty\r\n    let alarm_in_zone = alarm_weight >= zone.alarm_weight\r\n    let maybe_ok      = (state isnt @alarm) or (alarm_weight is 0)\r\n\r\n    if zone_is_empty      then state = \"none_unknown\"\r\n    else if alarm_in_zone then state = \"alarm_alarm\"\r\n    else if maybe_ok      then state = \"ok_normal\"\r\n                             \r\n    let changed = zone.state isnt state    \r\n    if not changes_only or changed or (zone.alarm_info is empty) then \r\n        update_alarm_info(zone, state, items, 0)\r\n\r\n    zone.state = state\r\nend\r\n\r\nlet process_state_by_precedence(zone as Zone) = \r\n    let changes_only      = settings.alarm_info.changes_only\r\n    let min_alarm_preced  = settings.precedence.alarm_alarm\r\n    let min_likely_preced = settings.precedence.alarm_likely\r\n    \r\n    let state = zone.state \r\n    let items = get_weighted_items(zone)\r\n                \r\n    let alarm_preced = from items where it.alarm select (weight * precedence * importance) sum\r\n\r\n    let zone_is_empty  = items is empty\r\n    let maybe_ok       = (state isnt @alarm) or (alarm_preced is 0)\r\n    let maybe_likely   = state isnt \"alarm_alarm\"\r\n    let maybe_unlikely = state isnt @alarm\r\n    \r\n    let alarm_in_zone    = (alarm_preced >= min_alarm_preced)\r\n    let likely_in_zone   = (alarm_preced >= min_likely_preced) and maybe_likely\r\n    let unlikely_in_zone = (alarm_preced >= 1)                 and maybe_unlikely   \r\n\r\n    if zone_is_empty         then state = \"none_unknown\"\r\n    else if alarm_in_zone    then state = \"alarm_alarm\"\r\n    else if likely_in_zone   then state = \"alarm_likely\"\r\n    else if unlikely_in_zone then state = \"alarm_unlikely\"\r\n    else if maybe_ok         then state = \"ok_normal\"\r\n                             \r\n    let changed = zone.state isnt state\r\n    if not changes_only or changed or (zone.alarm_info is empty) then \r\n        update_alarm_info(zone, state, items, alarm_preced)\r\n\r\n    zone.state = state\r\nend\r\n\r\nlet process_state(zone as Zone) = \r\n    if zone.alarm_weight > 0 then process_state_by_weight(zone) \r\n                             else process_state_by_precedence(zone)\r\nend\r\n\r\nlet process_zones() = from graph.Values of type Zone do process_state(it) now\r\n\r\nlet process_command(zone as Zone) = \r\n    from zone.zoneOutputPin.GetOppositeItems() 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                process_state(it) \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            process_state(link.@out.Owner as Zone)\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_weight\" in changes) then \r\n            process_state(zone)\r\n    end\r\n    \r\n    void()\r\nend\r\n\r\nthis.RunOnUpdated(on_update)\r\nthis.RunOnTimer(settings.interval.TotalSeconds, process_zones)\r\n\r\nprocess_zones()\r\n\r\n","datetime":1596700070,"status":null,"version":0},"name":{"value":"Smart Zone","datetime":1596644327,"status":null,"version":0}},"entity":"item","operation":"create"}]